@serfab/cadre-host 0.10.0 → 0.12.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 +406 -296
- package/dist/bin/host.d.ts +11 -7
- package/dist/bin/host.js +40 -12
- package/dist/bin/host.js.map +1 -1
- package/dist/installer/config.d.ts +4 -4
- package/dist/installer/config.js +5 -39
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/identity.d.ts +8 -1
- package/dist/installer/identity.js +8 -1
- package/dist/installer/identity.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +2 -2
- package/dist/orchestrator/host-process-orchestrator.js +9 -4
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/server/settings-store.d.ts +1 -1
- package/dist/server/settings-store.js +5 -1
- package/dist/server/settings-store.js.map +1 -1
- 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 +51 -46
- package/src/__tests__/global-setup.ts +56 -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 +5 -5
- 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/bin/__tests__/cli-reference.test.ts +167 -0
- package/src/bin/host.ts +1326 -1299
- 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__/config.test.ts +134 -112
- 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/config.ts +5 -43
- package/src/installer/identity.ts +56 -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 +18 -0
- 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 +9 -4
- 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 +195 -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 +5 -1
- 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,112 +1,134 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
|
|
6
|
-
import { readHostConfig, writeHostConfig, hostOwnsCadre, type HostConfigFile } from '../config.js';
|
|
7
|
-
|
|
8
|
-
describe('host.config.json round-trip', () => {
|
|
9
|
-
let tmp: string;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
tmp = mkdtempSync(join(tmpdir(), 'cadre-host-cfg-'));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
function makeCfg(overrides: Partial<HostConfigFile> = {}): HostConfigFile {
|
|
20
|
-
return {
|
|
21
|
-
version: 2,
|
|
22
|
-
installId: 'abc123',
|
|
23
|
-
uiPort: 8765,
|
|
24
|
-
libp2pPort: 4001,
|
|
25
|
-
dataDir: tmp,
|
|
26
|
-
identityPath: join(tmp, 'identity.key'),
|
|
27
|
-
upnpEnabled: true,
|
|
28
|
-
installedAt: '2026-01-01T00:00:00.000Z',
|
|
29
|
-
installerVersion: '0.6.0',
|
|
30
|
-
updates: { autoApply: false },
|
|
31
|
-
...overrides,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
it('writes and reads the same shape', () => {
|
|
36
|
-
const cfg = makeCfg();
|
|
37
|
-
const path = join(tmp, 'host.config.json');
|
|
38
|
-
writeHostConfig(path, cfg);
|
|
39
|
-
expect(readHostConfig(path)).toEqual(cfg);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('
|
|
43
|
-
const path = join(tmp, 'host.config.json');
|
|
44
|
-
const v1 = {
|
|
45
|
-
version: 1,
|
|
46
|
-
installId: 'abc123',
|
|
47
|
-
uiPort: 8765,
|
|
48
|
-
libp2pPort: 4001,
|
|
49
|
-
dataDir: tmp,
|
|
50
|
-
identityPath: join(tmp, 'identity.key'),
|
|
51
|
-
upnpEnabled: true,
|
|
52
|
-
installedAt: '2026-01-01T00:00:00.000Z',
|
|
53
|
-
installerVersion: '0.6.0',
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
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
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { readHostConfig, updateHostConfig, writeHostConfig, hostOwnsCadre, type HostConfigFile } from '../config.js';
|
|
7
|
+
|
|
8
|
+
describe('host.config.json round-trip', () => {
|
|
9
|
+
let tmp: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tmp = mkdtempSync(join(tmpdir(), 'cadre-host-cfg-'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function makeCfg(overrides: Partial<HostConfigFile> = {}): HostConfigFile {
|
|
20
|
+
return {
|
|
21
|
+
version: 2,
|
|
22
|
+
installId: 'abc123',
|
|
23
|
+
uiPort: 8765,
|
|
24
|
+
libp2pPort: 4001,
|
|
25
|
+
dataDir: tmp,
|
|
26
|
+
identityPath: join(tmp, 'identity.key'),
|
|
27
|
+
upnpEnabled: true,
|
|
28
|
+
installedAt: '2026-01-01T00:00:00.000Z',
|
|
29
|
+
installerVersion: '0.6.0',
|
|
30
|
+
updates: { autoApply: false },
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
it('writes and reads the same shape', () => {
|
|
36
|
+
const cfg = makeCfg();
|
|
37
|
+
const path = join(tmp, 'host.config.json');
|
|
38
|
+
writeHostConfig(path, cfg);
|
|
39
|
+
expect(readHostConfig(path)).toEqual(cfg);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('rejects v1 files without rewriting them', () => {
|
|
43
|
+
const path = join(tmp, 'host.config.json');
|
|
44
|
+
const v1 = {
|
|
45
|
+
version: 1,
|
|
46
|
+
installId: 'abc123',
|
|
47
|
+
uiPort: 8765,
|
|
48
|
+
libp2pPort: 4001,
|
|
49
|
+
dataDir: tmp,
|
|
50
|
+
identityPath: join(tmp, 'identity.key'),
|
|
51
|
+
upnpEnabled: true,
|
|
52
|
+
installedAt: '2026-01-01T00:00:00.000Z',
|
|
53
|
+
installerVersion: '0.6.0',
|
|
54
|
+
};
|
|
55
|
+
const raw = JSON.stringify(v1);
|
|
56
|
+
writeFileSync(path, raw);
|
|
57
|
+
expect(() => readHostConfig(path)).toThrow(/unsupported version=1/);
|
|
58
|
+
expect(readFileSync(path, 'utf8')).toBe(raw);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('rejects unknown versions', () => {
|
|
62
|
+
const path = join(tmp, 'host.config.json');
|
|
63
|
+
writeFileSync(path, JSON.stringify({ ...makeCfg(), version: 99 }));
|
|
64
|
+
expect(() => readHostConfig(path)).toThrow(/unsupported version=99/);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('rejects a file with no version field', () => {
|
|
68
|
+
const { version: _version, ...noVersion } = makeCfg();
|
|
69
|
+
const path = join(tmp, 'host.config.json');
|
|
70
|
+
writeFileSync(path, JSON.stringify(noVersion));
|
|
71
|
+
expect(() => readHostConfig(path)).toThrow(/unsupported version=undefined/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('rejects malformed JSON', () => {
|
|
75
|
+
const path = join(tmp, 'host.config.json');
|
|
76
|
+
writeFileSync(path, 'not json');
|
|
77
|
+
expect(() => readHostConfig(path)).toThrow(/Failed to parse/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('rejects missing required fields', () => {
|
|
81
|
+
const path = join(tmp, 'host.config.json');
|
|
82
|
+
writeFileSync(path, JSON.stringify({ version: 2, uiPort: 1234 }));
|
|
83
|
+
expect(() => readHostConfig(path)).toThrow(/missing required fields/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('refuses to write the wrong version', () => {
|
|
87
|
+
const path = join(tmp, 'host.config.json');
|
|
88
|
+
expect(() => writeHostConfig(path, { ...makeCfg(), version: 3 as never })).toThrow(/version=3/);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('update patches fields and re-stamps the current version', () => {
|
|
92
|
+
const path = join(tmp, 'host.config.json');
|
|
93
|
+
writeHostConfig(path, makeCfg());
|
|
94
|
+
const next = updateHostConfig(path, { updates: { autoApply: true } });
|
|
95
|
+
expect(next.version).toBe(2);
|
|
96
|
+
expect(next.updates).toEqual({ autoApply: true });
|
|
97
|
+
expect(next.installId).toBe('abc123');
|
|
98
|
+
expect(readHostConfig(path)).toEqual(next);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('update never lets a patch downgrade the version', () => {
|
|
102
|
+
const path = join(tmp, 'host.config.json');
|
|
103
|
+
writeHostConfig(path, makeCfg());
|
|
104
|
+
const patch = { version: 1 } as unknown as Partial<Omit<HostConfigFile, 'version'>>;
|
|
105
|
+
expect(updateHostConfig(path, patch).version).toBe(2);
|
|
106
|
+
expect(readHostConfig(path).version).toBe(2);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('round-trips ownCadre and hostOwnsCadre reflects it', () => {
|
|
110
|
+
const path = join(tmp, 'host.config.json');
|
|
111
|
+
const cfg = makeCfg({ ownCadre: { enabled: true } });
|
|
112
|
+
writeHostConfig(path, cfg);
|
|
113
|
+
const read = readHostConfig(path);
|
|
114
|
+
expect(read.ownCadre).toEqual({ enabled: true });
|
|
115
|
+
expect(hostOwnsCadre(read)).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('treats an absent ownCadre as donor-only (hostOwnsCadre false)', () => {
|
|
119
|
+
// A pre-ownCadre v2 config (field absent) must read back cleanly and be
|
|
120
|
+
// donor-only — no migration, field stays absent.
|
|
121
|
+
const path = join(tmp, 'host.config.json');
|
|
122
|
+
const cfg = makeCfg();
|
|
123
|
+
writeHostConfig(path, cfg);
|
|
124
|
+
const read = readHostConfig(path);
|
|
125
|
+
expect(read.ownCadre).toBeUndefined();
|
|
126
|
+
expect(hostOwnsCadre(read)).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('rejects a malformed ownCadre shape', () => {
|
|
130
|
+
const path = join(tmp, 'host.config.json');
|
|
131
|
+
writeFileSync(path, JSON.stringify({ ...makeCfg(), ownCadre: { enabled: 'yes' } }));
|
|
132
|
+
expect(() => readHostConfig(path)).toThrow(/missing required fields/);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -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('installer identity', () => {
|
|
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.js';
|
|
7
|
+
|
|
8
|
+
describe('installer identity', () => {
|
|
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,47 +1,47 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { defaultDataDir, serviceInstallPath } from '../paths.js';
|
|
4
|
-
|
|
5
|
-
describe('defaultDataDir', () => {
|
|
6
|
-
it('honors XDG_DATA_HOME on linux', () => {
|
|
7
|
-
expect(defaultDataDir('linux', { XDG_DATA_HOME: '/tmp/xdg', HOME: '/home/me' }, '/home/me'))
|
|
8
|
-
.toBe('/tmp/xdg/cadre-host');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('falls back to ~/.local/share on linux', () => {
|
|
12
|
-
expect(defaultDataDir('linux', { HOME: '/home/me' }, '/home/me'))
|
|
13
|
-
.toBe('/home/me/.local/share/cadre-host');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('uses Application Support on darwin', () => {
|
|
17
|
-
expect(defaultDataDir('darwin', { HOME: '/Users/me' }, '/Users/me'))
|
|
18
|
-
.toBe('/Users/me/Library/Application Support/CadreHost');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('uses LocalAppData on win32', () => {
|
|
22
|
-
expect(defaultDataDir('win32', { LocalAppData: 'C:\\Users\\me\\AppData\\Local' }, 'C:\\Users\\me'))
|
|
23
|
-
.toBe('C:\\Users\\me\\AppData\\Local\\CadreHost');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('falls back to ~/AppData/Local on win32 without LocalAppData', () => {
|
|
27
|
-
expect(defaultDataDir('win32', {}, 'C:\\Users\\me'))
|
|
28
|
-
.toBe('C:\\Users\\me\\AppData\\Local\\CadreHost');
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('serviceInstallPath', () => {
|
|
33
|
-
it('honors XDG_CONFIG_HOME on linux', () => {
|
|
34
|
-
expect(serviceInstallPath('linux', { XDG_CONFIG_HOME: '/tmp/cfg', HOME: '/home/me' }, '/home/me'))
|
|
35
|
-
.toBe('/tmp/cfg/systemd/user/cadre-host.service');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('uses LaunchAgents on darwin', () => {
|
|
39
|
-
expect(serviceInstallPath('darwin', { HOME: '/Users/me' }, '/Users/me'))
|
|
40
|
-
.toBe('/Users/me/Library/LaunchAgents/com.serfab.cadre-host.plist');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('returns a registry-style path on win32 (for logging only)', () => {
|
|
44
|
-
expect(serviceInstallPath('win32', {}, 'C:\\Users\\me'))
|
|
45
|
-
.toMatch(/CadreHost/);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { defaultDataDir, serviceInstallPath } from '../paths.js';
|
|
4
|
+
|
|
5
|
+
describe('defaultDataDir', () => {
|
|
6
|
+
it('honors XDG_DATA_HOME on linux', () => {
|
|
7
|
+
expect(defaultDataDir('linux', { XDG_DATA_HOME: '/tmp/xdg', HOME: '/home/me' }, '/home/me'))
|
|
8
|
+
.toBe('/tmp/xdg/cadre-host');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('falls back to ~/.local/share on linux', () => {
|
|
12
|
+
expect(defaultDataDir('linux', { HOME: '/home/me' }, '/home/me'))
|
|
13
|
+
.toBe('/home/me/.local/share/cadre-host');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('uses Application Support on darwin', () => {
|
|
17
|
+
expect(defaultDataDir('darwin', { HOME: '/Users/me' }, '/Users/me'))
|
|
18
|
+
.toBe('/Users/me/Library/Application Support/CadreHost');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('uses LocalAppData on win32', () => {
|
|
22
|
+
expect(defaultDataDir('win32', { LocalAppData: 'C:\\Users\\me\\AppData\\Local' }, 'C:\\Users\\me'))
|
|
23
|
+
.toBe('C:\\Users\\me\\AppData\\Local\\CadreHost');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('falls back to ~/AppData/Local on win32 without LocalAppData', () => {
|
|
27
|
+
expect(defaultDataDir('win32', {}, 'C:\\Users\\me'))
|
|
28
|
+
.toBe('C:\\Users\\me\\AppData\\Local\\CadreHost');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('serviceInstallPath', () => {
|
|
33
|
+
it('honors XDG_CONFIG_HOME on linux', () => {
|
|
34
|
+
expect(serviceInstallPath('linux', { XDG_CONFIG_HOME: '/tmp/cfg', HOME: '/home/me' }, '/home/me'))
|
|
35
|
+
.toBe('/tmp/cfg/systemd/user/cadre-host.service');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('uses LaunchAgents on darwin', () => {
|
|
39
|
+
expect(serviceInstallPath('darwin', { HOME: '/Users/me' }, '/Users/me'))
|
|
40
|
+
.toBe('/Users/me/Library/LaunchAgents/com.serfab.cadre-host.plist');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns a registry-style path on win32 (for logging only)', () => {
|
|
44
|
+
expect(serviceInstallPath('win32', {}, 'C:\\Users\\me'))
|
|
45
|
+
.toMatch(/CadreHost/);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
import { LaunchdServiceHost } from '../service-host/launchd.js';
|
|
6
|
-
import { SystemdServiceHost } from '../service-host/systemd.js';
|
|
7
|
-
import { NssmServiceHost } from '../service-host/nssm.js';
|
|
8
|
-
import { renderTemplate } from '../service-host/template.js';
|
|
9
|
-
import type { ServiceHostContext } from '../service-host/types.js';
|
|
10
|
-
|
|
11
|
-
const here = fileURLToPath(new URL('.', import.meta.url));
|
|
12
|
-
const serviceDir = resolve(here, '..', '..', '..', 'service');
|
|
13
|
-
|
|
14
|
-
function ctx(): ServiceHostContext {
|
|
15
|
-
return {
|
|
16
|
-
nodePath: '/usr/local/bin/node',
|
|
17
|
-
hostJs: '/opt/cadre-host/dist/bin/host.js',
|
|
18
|
-
dataDir: '/var/data/cadre-host',
|
|
19
|
-
serviceDir,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('renderTemplate', () => {
|
|
24
|
-
it('substitutes @TOKEN@ values', () => {
|
|
25
|
-
expect(renderTemplate('@A@ x @B@', { A: 'foo', B: 'bar' })).toBe('foo x bar');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('throws on unknown tokens', () => {
|
|
29
|
-
expect(() => renderTemplate('@A@ @MISSING@', { A: 'foo' })).toThrow(/missing value for token @MISSING@/);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('SystemdServiceHost.renderUnit', () => {
|
|
34
|
-
it('produces an enable-able [Install] section and substitutes ExecStart', () => {
|
|
35
|
-
const unit = new SystemdServiceHost().renderUnit(ctx());
|
|
36
|
-
expect(unit).toContain('[Install]');
|
|
37
|
-
expect(unit).toContain('WantedBy=default.target');
|
|
38
|
-
expect(unit).toContain('ExecStart=/usr/local/bin/node /opt/cadre-host/dist/bin/host.js start --no-tui --data-dir /var/data/cadre-host');
|
|
39
|
-
expect(unit).toContain('RestartPreventExitStatus=0');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('LaunchdServiceHost.renderUnit', () => {
|
|
44
|
-
it('produces a plist with the substituted ProgramArguments', () => {
|
|
45
|
-
const unit = new LaunchdServiceHost().renderUnit(ctx());
|
|
46
|
-
expect(unit).toContain('<key>Label</key>');
|
|
47
|
-
expect(unit).toContain('<string>com.serfab.cadre-host</string>');
|
|
48
|
-
expect(unit).toContain('<string>/usr/local/bin/node</string>');
|
|
49
|
-
expect(unit).toContain('<string>/opt/cadre-host/dist/bin/host.js</string>');
|
|
50
|
-
expect(unit).toContain('<string>--data-dir</string>');
|
|
51
|
-
expect(unit).toContain('<string>/var/data/cadre-host</string>');
|
|
52
|
-
expect(unit).toContain('<key>KeepAlive</key>');
|
|
53
|
-
expect(unit).toContain('<key>SuccessfulExit</key>');
|
|
54
|
-
expect(unit).toContain('<key>Crashed</key>');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('NssmServiceHost', () => {
|
|
59
|
-
it('returns null from renderUnit (NSSM stores config in the registry)', () => {
|
|
60
|
-
expect(new NssmServiceHost().renderUnit(ctx())).toBeNull();
|
|
61
|
-
});
|
|
62
|
-
});
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { LaunchdServiceHost } from '../service-host/launchd.js';
|
|
6
|
+
import { SystemdServiceHost } from '../service-host/systemd.js';
|
|
7
|
+
import { NssmServiceHost } from '../service-host/nssm.js';
|
|
8
|
+
import { renderTemplate } from '../service-host/template.js';
|
|
9
|
+
import type { ServiceHostContext } from '../service-host/types.js';
|
|
10
|
+
|
|
11
|
+
const here = fileURLToPath(new URL('.', import.meta.url));
|
|
12
|
+
const serviceDir = resolve(here, '..', '..', '..', 'service');
|
|
13
|
+
|
|
14
|
+
function ctx(): ServiceHostContext {
|
|
15
|
+
return {
|
|
16
|
+
nodePath: '/usr/local/bin/node',
|
|
17
|
+
hostJs: '/opt/cadre-host/dist/bin/host.js',
|
|
18
|
+
dataDir: '/var/data/cadre-host',
|
|
19
|
+
serviceDir,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('renderTemplate', () => {
|
|
24
|
+
it('substitutes @TOKEN@ values', () => {
|
|
25
|
+
expect(renderTemplate('@A@ x @B@', { A: 'foo', B: 'bar' })).toBe('foo x bar');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('throws on unknown tokens', () => {
|
|
29
|
+
expect(() => renderTemplate('@A@ @MISSING@', { A: 'foo' })).toThrow(/missing value for token @MISSING@/);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('SystemdServiceHost.renderUnit', () => {
|
|
34
|
+
it('produces an enable-able [Install] section and substitutes ExecStart', () => {
|
|
35
|
+
const unit = new SystemdServiceHost().renderUnit(ctx());
|
|
36
|
+
expect(unit).toContain('[Install]');
|
|
37
|
+
expect(unit).toContain('WantedBy=default.target');
|
|
38
|
+
expect(unit).toContain('ExecStart=/usr/local/bin/node /opt/cadre-host/dist/bin/host.js start --no-tui --data-dir /var/data/cadre-host');
|
|
39
|
+
expect(unit).toContain('RestartPreventExitStatus=0');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('LaunchdServiceHost.renderUnit', () => {
|
|
44
|
+
it('produces a plist with the substituted ProgramArguments', () => {
|
|
45
|
+
const unit = new LaunchdServiceHost().renderUnit(ctx());
|
|
46
|
+
expect(unit).toContain('<key>Label</key>');
|
|
47
|
+
expect(unit).toContain('<string>com.serfab.cadre-host</string>');
|
|
48
|
+
expect(unit).toContain('<string>/usr/local/bin/node</string>');
|
|
49
|
+
expect(unit).toContain('<string>/opt/cadre-host/dist/bin/host.js</string>');
|
|
50
|
+
expect(unit).toContain('<string>--data-dir</string>');
|
|
51
|
+
expect(unit).toContain('<string>/var/data/cadre-host</string>');
|
|
52
|
+
expect(unit).toContain('<key>KeepAlive</key>');
|
|
53
|
+
expect(unit).toContain('<key>SuccessfulExit</key>');
|
|
54
|
+
expect(unit).toContain('<key>Crashed</key>');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('NssmServiceHost', () => {
|
|
59
|
+
it('returns null from renderUnit (NSSM stores config in the registry)', () => {
|
|
60
|
+
expect(new NssmServiceHost().renderUnit(ctx())).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
});
|