@serfab/cadre-host 0.10.0 → 0.11.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/dist/server/static.js +38 -38
- package/dist/ui/assets/index-C-iks41M.js.map +1 -1
- package/dist/ui/index.html +12 -12
- package/package.json +4 -4
- package/service/cadre-host.service.tmpl +16 -16
- package/service/com.serfab.cadre-host.plist.tmpl +35 -35
- package/service/install-service.ps1 +98 -98
- package/service/uninstall-service.ps1 +19 -19
- package/src/__tests__/build-targets.test.ts +26 -26
- package/src/__tests__/cli-invite.smoke.test.ts +192 -192
- package/src/__tests__/cli-nat.smoke.test.ts +212 -212
- package/src/__tests__/cli.smoke.test.ts +46 -46
- package/src/__tests__/global-setup.ts +51 -51
- package/src/__tests__/host-owner.smoke.test.ts +123 -123
- package/src/__tests__/orchestrator-env-scrub.test.ts +140 -140
- package/src/__tests__/orchestrator-node-identity.test.ts +204 -204
- package/src/__tests__/orchestrator-owner.test.ts +198 -198
- package/src/__tests__/orchestrator-pin-keys.test.ts +153 -153
- package/src/__tests__/orchestrator-push.test.ts +145 -145
- package/src/__tests__/orchestrator.test.ts +951 -951
- package/src/auth/__tests__/duration.test.ts +28 -28
- package/src/auth/__tests__/self-label.test.ts +85 -85
- package/src/auth/__tests__/trust-circle-store.test.ts +115 -115
- package/src/auth/__tests__/trust-circle.test.ts +445 -445
- package/src/auth/duration.ts +33 -33
- package/src/auth/index.ts +21 -21
- package/src/auth/self-label.ts +74 -74
- package/src/auth/trust-circle-store.ts +155 -155
- package/src/auth/types.ts +99 -99
- package/src/donation/__tests__/donation-service.test.ts +1068 -1068
- package/src/donation/__tests__/donation-supervisor.test.ts +510 -510
- package/src/donation/__tests__/fake-orchestrator.test.ts +168 -168
- package/src/donation/__tests__/fake-orchestrator.ts +225 -225
- package/src/donation/__tests__/grant-service.test.ts +213 -213
- package/src/donation/__tests__/grant-store.test.ts +113 -113
- package/src/donation/donation-service.ts +962 -962
- package/src/donation/donation-store.ts +171 -171
- package/src/donation/donation-supervisor.ts +388 -388
- package/src/donation/grant-service.ts +200 -200
- package/src/donation/grant-store.ts +152 -152
- package/src/donation/index.ts +64 -64
- package/src/donation/types.ts +243 -243
- package/src/installer/__tests__/identity.test.ts +34 -34
- package/src/installer/__tests__/paths.test.ts +47 -47
- package/src/installer/__tests__/service-host.test.ts +62 -62
- package/src/installer/__tests__/wizard.test.ts +64 -64
- package/src/installer/identity.ts +49 -49
- package/src/installer/paths.ts +109 -109
- package/src/installer/platform.ts +27 -27
- package/src/installer/service-host/index.ts +21 -21
- package/src/installer/service-host/template.ts +21 -21
- package/src/installer/wizard.ts +103 -103
- package/src/nat/__tests__/address-resolver.test.ts +82 -82
- package/src/nat/__tests__/ddns-duckdns.test.ts +83 -83
- package/src/nat/__tests__/ddns-updater.test.ts +253 -253
- package/src/nat/__tests__/external-ip.test.ts +124 -124
- package/src/nat/__tests__/nat-service.test.ts +497 -497
- package/src/nat/__tests__/nat-store.test.ts +93 -93
- package/src/nat/__tests__/port-mapper.test.ts +191 -191
- package/src/nat/__tests__/reachability.test.ts +24 -24
- package/src/nat/__tests__/secrets.test.ts +178 -178
- package/src/nat/address-resolver.ts +51 -51
- package/src/nat/ddns/duckdns.ts +76 -76
- package/src/nat/ddns/index.ts +55 -55
- package/src/nat/ddns/updater.ts +243 -243
- package/src/nat/external-ip.ts +154 -154
- package/src/nat/index.ts +71 -71
- package/src/nat/nat-service.ts +501 -501
- package/src/nat/nat-store.ts +154 -154
- package/src/nat/port-mapper.ts +329 -329
- package/src/nat/reachability.ts +34 -34
- package/src/nat/secrets/file-store.ts +130 -130
- package/src/nat/secrets/index.ts +112 -112
- package/src/nat/secrets/keytar-store.ts +67 -67
- package/src/nat/types.ts +159 -159
- package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
- package/src/orchestrator/host-process-orchestrator.ts +1133 -1133
- package/src/orchestrator/index.ts +10 -10
- package/src/orchestrator/log-rotator.ts +53 -53
- package/src/orchestrator/node-identity.ts +40 -40
- package/src/orchestrator/pid-liveness.ts +24 -24
- package/src/orchestrator/port-allocator.ts +91 -91
- package/src/orchestrator/state-store.ts +90 -90
- package/src/orchestrator/types.ts +141 -141
- package/src/owner/__tests__/owner-node-client.test.ts +194 -194
- package/src/owner/index.ts +12 -12
- package/src/owner/owner-node-client.ts +259 -259
- package/src/push/__tests__/push.test.ts +90 -90
- package/src/push/index.ts +146 -146
- package/src/server/__tests__/bus.test.ts +38 -38
- package/src/server/__tests__/error-handler.test.ts +118 -118
- package/src/server/__tests__/grants-admin-route.test.ts +73 -73
- package/src/server/__tests__/grants-route.test.ts +263 -263
- package/src/server/__tests__/nodes-route.test.ts +259 -259
- package/src/server/__tests__/origin-guard.test.ts +72 -72
- package/src/server/__tests__/publishers.test.ts +216 -216
- package/src/server/__tests__/server.smoke.test.ts +253 -253
- package/src/server/__tests__/settings-route.test.ts +194 -194
- package/src/server/__tests__/sse-route.test.ts +119 -119
- package/src/server/__tests__/static.test.ts +145 -145
- package/src/server/__tests__/status-route.test.ts +134 -134
- package/src/server/__tests__/strands-route.test.ts +233 -233
- package/src/server/error-handler.ts +178 -178
- package/src/server/events/bus.ts +42 -42
- package/src/server/events/sse-route.ts +81 -81
- package/src/server/events/types.ts +23 -23
- package/src/server/index.ts +267 -267
- package/src/server/origin-guard.ts +84 -84
- package/src/server/routes/grants-admin.ts +41 -41
- package/src/server/routes/grants.ts +210 -210
- package/src/server/routes/nat.ts +63 -63
- package/src/server/routes/nodes.ts +170 -170
- package/src/server/routes/settings.ts +123 -123
- package/src/server/routes/status.ts +118 -118
- package/src/server/routes/strands.ts +54 -54
- package/src/server/routes/trust-circle.ts +49 -49
- package/src/server/routes/update.ts +43 -43
- package/src/server/server.ts +122 -122
- package/src/server/settings-store.ts +37 -37
- package/src/server/static.ts +207 -207
- package/src/strands/__tests__/strand-service.test.ts +165 -165
- package/src/strands/index.ts +14 -14
- package/src/strands/strand-service.ts +135 -135
- package/src/strands/types.ts +102 -102
- package/src/update/__tests__/apply.test.ts +158 -158
- package/src/update/__tests__/manifest.test.ts +128 -128
- package/src/update/__tests__/release-key.test.ts +42 -42
- package/src/update/__tests__/sign.test.ts +78 -78
- package/src/update/__tests__/store.test.ts +81 -81
- package/src/update/__tests__/update-service.integration.test.ts +256 -256
- package/src/update/__tests__/version.test.ts +31 -31
- package/src/update/apply.ts +190 -190
- package/src/update/index.ts +341 -341
- package/src/update/release-key.ts +71 -71
- package/src/update/sign.ts +74 -74
- package/src/update/store.ts +101 -101
- package/src/update/types.ts +109 -109
- package/src/update/version.ts +80 -80
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { ExternalIpDetector, isPlausibleIp } from '../external-ip.js';
|
|
4
|
-
|
|
5
|
-
function makeFetchSequence(responses: Array<{ ok: boolean; body: string }>): typeof fetch {
|
|
6
|
-
let i = 0;
|
|
7
|
-
return (async () => {
|
|
8
|
-
const r = responses[i++] ?? { ok: false, body: '' };
|
|
9
|
-
return {
|
|
10
|
-
ok: r.ok,
|
|
11
|
-
status: r.ok ? 200 : 500,
|
|
12
|
-
async text() { return r.body; },
|
|
13
|
-
} as unknown as Response;
|
|
14
|
-
}) as typeof fetch;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('isPlausibleIp()', () => {
|
|
18
|
-
it('accepts IPv4', () => {
|
|
19
|
-
expect(isPlausibleIp('1.2.3.4')).toBe(true);
|
|
20
|
-
expect(isPlausibleIp('255.255.255.255')).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
it('rejects bad IPv4', () => {
|
|
23
|
-
expect(isPlausibleIp('256.0.0.1')).toBe(false);
|
|
24
|
-
expect(isPlausibleIp('1.2.3')).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
it('accepts IPv6 liberally', () => {
|
|
27
|
-
expect(isPlausibleIp('::1')).toBe(true);
|
|
28
|
-
expect(isPlausibleIp('2001:db8::1')).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
it('rejects junk', () => {
|
|
31
|
-
expect(isPlausibleIp('not an ip')).toBe(false);
|
|
32
|
-
expect(isPlausibleIp('')).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('ExternalIpDetector', () => {
|
|
37
|
-
it('returns null routerIp when no probe configured', async () => {
|
|
38
|
-
const det = new ExternalIpDetector({
|
|
39
|
-
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
40
|
-
publicIpUrls: ['https://example.test'],
|
|
41
|
-
});
|
|
42
|
-
const r = await det.detect();
|
|
43
|
-
expect(r.routerIp).toBeNull();
|
|
44
|
-
expect(r.publicIp).toBe('1.2.3.4');
|
|
45
|
-
expect(r.cgnat).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('detects CGNAT when router and public IP disagree', async () => {
|
|
49
|
-
const det = new ExternalIpDetector({
|
|
50
|
-
routerProbe: async () => '100.64.0.5', // CGNAT space — fine for testing
|
|
51
|
-
fetch: makeFetchSequence([{ ok: true, body: '203.0.113.10' }]),
|
|
52
|
-
publicIpUrls: ['https://example.test'],
|
|
53
|
-
});
|
|
54
|
-
const r = await det.detect();
|
|
55
|
-
expect(r.routerIp).toBe('100.64.0.5');
|
|
56
|
-
expect(r.publicIp).toBe('203.0.113.10');
|
|
57
|
-
expect(r.cgnat).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('does NOT flag CGNAT when IPs agree', async () => {
|
|
61
|
-
const det = new ExternalIpDetector({
|
|
62
|
-
routerProbe: async () => '1.2.3.4',
|
|
63
|
-
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
64
|
-
publicIpUrls: ['https://example.test'],
|
|
65
|
-
});
|
|
66
|
-
const r = await det.detect();
|
|
67
|
-
expect(r.cgnat).toBe(false);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('does NOT flag CGNAT when one probe fails', async () => {
|
|
71
|
-
const det = new ExternalIpDetector({
|
|
72
|
-
routerProbe: async () => '1.2.3.4',
|
|
73
|
-
fetch: makeFetchSequence([{ ok: false, body: '' }]),
|
|
74
|
-
publicIpUrls: ['https://example.test'],
|
|
75
|
-
});
|
|
76
|
-
const r = await det.detect();
|
|
77
|
-
expect(r.routerIp).toBe('1.2.3.4');
|
|
78
|
-
expect(r.publicIp).toBeNull();
|
|
79
|
-
expect(r.cgnat).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('falls through public probe URLs until one succeeds', async () => {
|
|
83
|
-
const det = new ExternalIpDetector({
|
|
84
|
-
fetch: makeFetchSequence([
|
|
85
|
-
{ ok: false, body: '' },
|
|
86
|
-
{ ok: true, body: '8.8.8.8' },
|
|
87
|
-
]),
|
|
88
|
-
publicIpUrls: ['https://a.test', 'https://b.test'],
|
|
89
|
-
});
|
|
90
|
-
const r = await det.detect();
|
|
91
|
-
expect(r.publicIp).toBe('8.8.8.8');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('treats router probe throw as null', async () => {
|
|
95
|
-
const det = new ExternalIpDetector({
|
|
96
|
-
routerProbe: async () => { throw new Error('upnp not reachable'); },
|
|
97
|
-
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
98
|
-
publicIpUrls: ['https://a.test'],
|
|
99
|
-
});
|
|
100
|
-
const r = await det.detect();
|
|
101
|
-
expect(r.routerIp).toBeNull();
|
|
102
|
-
expect(r.publicIp).toBe('1.2.3.4');
|
|
103
|
-
expect(r.cgnat).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('rejects implausible router-probe IP', async () => {
|
|
107
|
-
const det = new ExternalIpDetector({
|
|
108
|
-
routerProbe: async () => 'not-an-ip',
|
|
109
|
-
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
110
|
-
publicIpUrls: ['https://a.test'],
|
|
111
|
-
});
|
|
112
|
-
const r = await det.detect();
|
|
113
|
-
expect(r.routerIp).toBeNull();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('rejects non-IP public-service body', async () => {
|
|
117
|
-
const det = new ExternalIpDetector({
|
|
118
|
-
fetch: makeFetchSequence([{ ok: true, body: '<html>error</html>' }]),
|
|
119
|
-
publicIpUrls: ['https://a.test'],
|
|
120
|
-
});
|
|
121
|
-
const r = await det.detect();
|
|
122
|
-
expect(r.publicIp).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ExternalIpDetector, isPlausibleIp } from '../external-ip.js';
|
|
4
|
+
|
|
5
|
+
function makeFetchSequence(responses: Array<{ ok: boolean; body: string }>): typeof fetch {
|
|
6
|
+
let i = 0;
|
|
7
|
+
return (async () => {
|
|
8
|
+
const r = responses[i++] ?? { ok: false, body: '' };
|
|
9
|
+
return {
|
|
10
|
+
ok: r.ok,
|
|
11
|
+
status: r.ok ? 200 : 500,
|
|
12
|
+
async text() { return r.body; },
|
|
13
|
+
} as unknown as Response;
|
|
14
|
+
}) as typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('isPlausibleIp()', () => {
|
|
18
|
+
it('accepts IPv4', () => {
|
|
19
|
+
expect(isPlausibleIp('1.2.3.4')).toBe(true);
|
|
20
|
+
expect(isPlausibleIp('255.255.255.255')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('rejects bad IPv4', () => {
|
|
23
|
+
expect(isPlausibleIp('256.0.0.1')).toBe(false);
|
|
24
|
+
expect(isPlausibleIp('1.2.3')).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it('accepts IPv6 liberally', () => {
|
|
27
|
+
expect(isPlausibleIp('::1')).toBe(true);
|
|
28
|
+
expect(isPlausibleIp('2001:db8::1')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('rejects junk', () => {
|
|
31
|
+
expect(isPlausibleIp('not an ip')).toBe(false);
|
|
32
|
+
expect(isPlausibleIp('')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('ExternalIpDetector', () => {
|
|
37
|
+
it('returns null routerIp when no probe configured', async () => {
|
|
38
|
+
const det = new ExternalIpDetector({
|
|
39
|
+
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
40
|
+
publicIpUrls: ['https://example.test'],
|
|
41
|
+
});
|
|
42
|
+
const r = await det.detect();
|
|
43
|
+
expect(r.routerIp).toBeNull();
|
|
44
|
+
expect(r.publicIp).toBe('1.2.3.4');
|
|
45
|
+
expect(r.cgnat).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('detects CGNAT when router and public IP disagree', async () => {
|
|
49
|
+
const det = new ExternalIpDetector({
|
|
50
|
+
routerProbe: async () => '100.64.0.5', // CGNAT space — fine for testing
|
|
51
|
+
fetch: makeFetchSequence([{ ok: true, body: '203.0.113.10' }]),
|
|
52
|
+
publicIpUrls: ['https://example.test'],
|
|
53
|
+
});
|
|
54
|
+
const r = await det.detect();
|
|
55
|
+
expect(r.routerIp).toBe('100.64.0.5');
|
|
56
|
+
expect(r.publicIp).toBe('203.0.113.10');
|
|
57
|
+
expect(r.cgnat).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('does NOT flag CGNAT when IPs agree', async () => {
|
|
61
|
+
const det = new ExternalIpDetector({
|
|
62
|
+
routerProbe: async () => '1.2.3.4',
|
|
63
|
+
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
64
|
+
publicIpUrls: ['https://example.test'],
|
|
65
|
+
});
|
|
66
|
+
const r = await det.detect();
|
|
67
|
+
expect(r.cgnat).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('does NOT flag CGNAT when one probe fails', async () => {
|
|
71
|
+
const det = new ExternalIpDetector({
|
|
72
|
+
routerProbe: async () => '1.2.3.4',
|
|
73
|
+
fetch: makeFetchSequence([{ ok: false, body: '' }]),
|
|
74
|
+
publicIpUrls: ['https://example.test'],
|
|
75
|
+
});
|
|
76
|
+
const r = await det.detect();
|
|
77
|
+
expect(r.routerIp).toBe('1.2.3.4');
|
|
78
|
+
expect(r.publicIp).toBeNull();
|
|
79
|
+
expect(r.cgnat).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('falls through public probe URLs until one succeeds', async () => {
|
|
83
|
+
const det = new ExternalIpDetector({
|
|
84
|
+
fetch: makeFetchSequence([
|
|
85
|
+
{ ok: false, body: '' },
|
|
86
|
+
{ ok: true, body: '8.8.8.8' },
|
|
87
|
+
]),
|
|
88
|
+
publicIpUrls: ['https://a.test', 'https://b.test'],
|
|
89
|
+
});
|
|
90
|
+
const r = await det.detect();
|
|
91
|
+
expect(r.publicIp).toBe('8.8.8.8');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('treats router probe throw as null', async () => {
|
|
95
|
+
const det = new ExternalIpDetector({
|
|
96
|
+
routerProbe: async () => { throw new Error('upnp not reachable'); },
|
|
97
|
+
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
98
|
+
publicIpUrls: ['https://a.test'],
|
|
99
|
+
});
|
|
100
|
+
const r = await det.detect();
|
|
101
|
+
expect(r.routerIp).toBeNull();
|
|
102
|
+
expect(r.publicIp).toBe('1.2.3.4');
|
|
103
|
+
expect(r.cgnat).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('rejects implausible router-probe IP', async () => {
|
|
107
|
+
const det = new ExternalIpDetector({
|
|
108
|
+
routerProbe: async () => 'not-an-ip',
|
|
109
|
+
fetch: makeFetchSequence([{ ok: true, body: '1.2.3.4' }]),
|
|
110
|
+
publicIpUrls: ['https://a.test'],
|
|
111
|
+
});
|
|
112
|
+
const r = await det.detect();
|
|
113
|
+
expect(r.routerIp).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('rejects non-IP public-service body', async () => {
|
|
117
|
+
const det = new ExternalIpDetector({
|
|
118
|
+
fetch: makeFetchSequence([{ ok: true, body: '<html>error</html>' }]),
|
|
119
|
+
publicIpUrls: ['https://a.test'],
|
|
120
|
+
});
|
|
121
|
+
const r = await det.detect();
|
|
122
|
+
expect(r.publicIp).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
});
|