@serfab/cadre-host 0.7.1
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 +101 -0
- package/dist/auth/duration.d.ts +1 -0
- package/dist/auth/duration.js +32 -0
- package/dist/auth/duration.js.map +1 -0
- package/dist/auth/index.d.ts +9 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/trust-circle-store.d.ts +26 -0
- package/dist/auth/trust-circle-store.js +129 -0
- package/dist/auth/trust-circle-store.js.map +1 -0
- package/dist/auth/trust-circle.d.ts +91 -0
- package/dist/auth/trust-circle.js +230 -0
- package/dist/auth/trust-circle.js.map +1 -0
- package/dist/auth/types.d.ts +81 -0
- package/dist/auth/types.js +24 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/bin/host.d.ts +13 -0
- package/dist/bin/host.js +826 -0
- package/dist/bin/host.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/browser.d.ts +14 -0
- package/dist/installer/browser.js +35 -0
- package/dist/installer/browser.js.map +1 -0
- package/dist/installer/config.d.ts +43 -0
- package/dist/installer/config.js +113 -0
- package/dist/installer/config.js.map +1 -0
- package/dist/installer/identity.d.ts +23 -0
- package/dist/installer/identity.js +36 -0
- package/dist/installer/identity.js.map +1 -0
- package/dist/installer/index.d.ts +83 -0
- package/dist/installer/index.js +204 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/installer/paths.d.ts +33 -0
- package/dist/installer/paths.js +84 -0
- package/dist/installer/paths.js.map +1 -0
- package/dist/installer/platform.d.ts +9 -0
- package/dist/installer/platform.js +21 -0
- package/dist/installer/platform.js.map +1 -0
- package/dist/installer/service-host/index.d.ts +11 -0
- package/dist/installer/service-host/index.js +16 -0
- package/dist/installer/service-host/index.js.map +1 -0
- package/dist/installer/service-host/launchd.d.ts +16 -0
- package/dist/installer/service-host/launchd.js +79 -0
- package/dist/installer/service-host/launchd.js.map +1 -0
- package/dist/installer/service-host/nssm.d.ts +20 -0
- package/dist/installer/service-host/nssm.js +88 -0
- package/dist/installer/service-host/nssm.js.map +1 -0
- package/dist/installer/service-host/systemd.d.ts +16 -0
- package/dist/installer/service-host/systemd.js +78 -0
- package/dist/installer/service-host/systemd.js.map +1 -0
- package/dist/installer/service-host/template.d.ts +6 -0
- package/dist/installer/service-host/template.js +20 -0
- package/dist/installer/service-host/template.js.map +1 -0
- package/dist/installer/service-host/types.d.ts +40 -0
- package/dist/installer/service-host/types.js +6 -0
- package/dist/installer/service-host/types.js.map +1 -0
- package/dist/installer/wizard.d.ts +31 -0
- package/dist/installer/wizard.js +67 -0
- package/dist/installer/wizard.js.map +1 -0
- package/dist/nat/address-resolver.d.ts +22 -0
- package/dist/nat/address-resolver.js +31 -0
- package/dist/nat/address-resolver.js.map +1 -0
- package/dist/nat/ddns/duckdns.d.ts +12 -0
- package/dist/nat/ddns/duckdns.js +57 -0
- package/dist/nat/ddns/duckdns.js.map +1 -0
- package/dist/nat/ddns/index.d.ts +30 -0
- package/dist/nat/ddns/index.js +21 -0
- package/dist/nat/ddns/index.js.map +1 -0
- package/dist/nat/ddns/updater.d.ts +68 -0
- package/dist/nat/ddns/updater.js +194 -0
- package/dist/nat/ddns/updater.js.map +1 -0
- package/dist/nat/external-ip.d.ts +62 -0
- package/dist/nat/external-ip.js +114 -0
- package/dist/nat/external-ip.js.map +1 -0
- package/dist/nat/index.d.ts +22 -0
- package/dist/nat/index.js +16 -0
- package/dist/nat/index.js.map +1 -0
- package/dist/nat/nat-service.d.ts +101 -0
- package/dist/nat/nat-service.js +284 -0
- package/dist/nat/nat-service.js.map +1 -0
- package/dist/nat/nat-store.d.ts +19 -0
- package/dist/nat/nat-store.js +136 -0
- package/dist/nat/nat-store.js.map +1 -0
- package/dist/nat/port-mapper.d.ts +98 -0
- package/dist/nat/port-mapper.js +226 -0
- package/dist/nat/port-mapper.js.map +1 -0
- package/dist/nat/reachability.d.ts +19 -0
- package/dist/nat/reachability.js +29 -0
- package/dist/nat/reachability.js.map +1 -0
- package/dist/nat/secrets/file-store.d.ts +23 -0
- package/dist/nat/secrets/file-store.js +102 -0
- package/dist/nat/secrets/file-store.js.map +1 -0
- package/dist/nat/secrets/index.d.ts +36 -0
- package/dist/nat/secrets/index.js +56 -0
- package/dist/nat/secrets/index.js.map +1 -0
- package/dist/nat/secrets/keytar-store.d.ts +18 -0
- package/dist/nat/secrets/keytar-store.js +52 -0
- package/dist/nat/secrets/keytar-store.js.map +1 -0
- package/dist/nat/types.d.ts +120 -0
- package/dist/nat/types.js +24 -0
- package/dist/nat/types.js.map +1 -0
- package/dist/orchestrator/host-process-orchestrator.d.ts +87 -0
- package/dist/orchestrator/host-process-orchestrator.js +557 -0
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -0
- package/dist/orchestrator/index.d.ts +3 -0
- package/dist/orchestrator/index.js +2 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/log-rotator.d.ts +25 -0
- package/dist/orchestrator/log-rotator.js +56 -0
- package/dist/orchestrator/log-rotator.js.map +1 -0
- package/dist/orchestrator/pid-liveness.d.ts +11 -0
- package/dist/orchestrator/pid-liveness.js +26 -0
- package/dist/orchestrator/pid-liveness.js.map +1 -0
- package/dist/orchestrator/port-allocator.d.ts +16 -0
- package/dist/orchestrator/port-allocator.js +38 -0
- package/dist/orchestrator/port-allocator.js.map +1 -0
- package/dist/orchestrator/state-store.d.ts +32 -0
- package/dist/orchestrator/state-store.js +44 -0
- package/dist/orchestrator/state-store.js.map +1 -0
- package/dist/orchestrator/types.d.ts +85 -0
- package/dist/orchestrator/types.js +31 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/dist/server/error-handler.d.ts +26 -0
- package/dist/server/error-handler.js +95 -0
- package/dist/server/error-handler.js.map +1 -0
- package/dist/server/events/bus.d.ts +17 -0
- package/dist/server/events/bus.js +36 -0
- package/dist/server/events/bus.js.map +1 -0
- package/dist/server/events/sse-route.d.ts +22 -0
- package/dist/server/events/sse-route.js +65 -0
- package/dist/server/events/sse-route.js.map +1 -0
- package/dist/server/events/types.d.ts +25 -0
- package/dist/server/events/types.js +8 -0
- package/dist/server/events/types.js.map +1 -0
- package/dist/server/index.d.ts +67 -0
- package/dist/server/index.js +149 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/origin-guard.d.ts +24 -0
- package/dist/server/origin-guard.js +72 -0
- package/dist/server/origin-guard.js.map +1 -0
- package/dist/server/routes/nat.d.ts +16 -0
- package/dist/server/routes/nat.js +42 -0
- package/dist/server/routes/nat.js.map +1 -0
- package/dist/server/routes/nodes.d.ts +23 -0
- package/dist/server/routes/nodes.js +128 -0
- package/dist/server/routes/nodes.js.map +1 -0
- package/dist/server/routes/settings.d.ts +26 -0
- package/dist/server/routes/settings.js +97 -0
- package/dist/server/routes/settings.js.map +1 -0
- package/dist/server/routes/status.d.ts +42 -0
- package/dist/server/routes/status.js +76 -0
- package/dist/server/routes/status.js.map +1 -0
- package/dist/server/routes/trust-circle.d.ts +17 -0
- package/dist/server/routes/trust-circle.js +35 -0
- package/dist/server/routes/trust-circle.js.map +1 -0
- package/dist/server/routes/update.d.ts +16 -0
- package/dist/server/routes/update.js +29 -0
- package/dist/server/routes/update.js.map +1 -0
- package/dist/server/server.d.ts +40 -0
- package/dist/server/server.js +90 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/settings-store.d.ts +24 -0
- package/dist/server/settings-store.js +27 -0
- package/dist/server/settings-store.js.map +1 -0
- package/dist/server/static.d.ts +25 -0
- package/dist/server/static.js +177 -0
- package/dist/server/static.js.map +1 -0
- package/dist/ui/assets/index-BwU5mJGF.css +1 -0
- package/dist/ui/assets/index-C9uChk2P.js +14 -0
- package/dist/ui/assets/index-C9uChk2P.js.map +1 -0
- package/dist/ui/index.html +14 -0
- package/dist/update/apply.d.ts +37 -0
- package/dist/update/apply.js +102 -0
- package/dist/update/apply.js.map +1 -0
- package/dist/update/index.d.ts +101 -0
- package/dist/update/index.js +228 -0
- package/dist/update/index.js.map +1 -0
- package/dist/update/manifest.d.ts +48 -0
- package/dist/update/manifest.js +152 -0
- package/dist/update/manifest.js.map +1 -0
- package/dist/update/release-key.d.ts +24 -0
- package/dist/update/release-key.js +52 -0
- package/dist/update/release-key.js.map +1 -0
- package/dist/update/store.d.ts +22 -0
- package/dist/update/store.js +81 -0
- package/dist/update/store.js.map +1 -0
- package/dist/update/types.d.ts +90 -0
- package/dist/update/types.js +22 -0
- package/dist/update/types.js.map +1 -0
- package/dist/update/version.d.ts +18 -0
- package/dist/update/version.js +76 -0
- package/dist/update/version.js.map +1 -0
- package/package.json +80 -0
- package/service/README.md +86 -0
- package/service/cadre-host.service.tmpl +16 -0
- package/service/com.serfab.cadre-host.plist.tmpl +35 -0
- package/service/install-service.ps1 +98 -0
- package/service/uninstall-service.ps1 +19 -0
- package/src/__tests__/cli-invite.smoke.test.ts +187 -0
- package/src/__tests__/cli-nat.smoke.test.ts +207 -0
- package/src/__tests__/cli.smoke.test.ts +41 -0
- package/src/__tests__/orchestrator.test.ts +570 -0
- package/src/auth/__tests__/duration.test.ts +28 -0
- package/src/auth/__tests__/trust-circle-integration.test.ts +128 -0
- package/src/auth/__tests__/trust-circle-store.test.ts +115 -0
- package/src/auth/__tests__/trust-circle.test.ts +334 -0
- package/src/auth/duration.ts +33 -0
- package/src/auth/index.ts +19 -0
- package/src/auth/trust-circle-store.ts +155 -0
- package/src/auth/trust-circle.ts +304 -0
- package/src/auth/types.ts +96 -0
- package/src/bin/host.ts +905 -0
- package/src/index.ts +167 -0
- package/src/installer/__tests__/config.test.ts +86 -0
- package/src/installer/__tests__/identity.test.ts +34 -0
- package/src/installer/__tests__/installer.smoke.test.ts +124 -0
- package/src/installer/__tests__/paths.test.ts +47 -0
- package/src/installer/__tests__/service-host.test.ts +62 -0
- package/src/installer/__tests__/wizard.test.ts +60 -0
- package/src/installer/browser.ts +42 -0
- package/src/installer/config.ts +153 -0
- package/src/installer/identity.ts +49 -0
- package/src/installer/index.ts +303 -0
- package/src/installer/paths.ts +109 -0
- package/src/installer/platform.ts +27 -0
- package/src/installer/service-host/index.ts +21 -0
- package/src/installer/service-host/launchd.ts +90 -0
- package/src/installer/service-host/nssm.ts +100 -0
- package/src/installer/service-host/systemd.ts +88 -0
- package/src/installer/service-host/template.ts +21 -0
- package/src/installer/service-host/types.ts +48 -0
- package/src/installer/wizard.ts +92 -0
- package/src/nat/__tests__/address-resolver.test.ts +82 -0
- package/src/nat/__tests__/ddns-duckdns.test.ts +83 -0
- package/src/nat/__tests__/ddns-updater.test.ts +253 -0
- package/src/nat/__tests__/external-ip.test.ts +124 -0
- package/src/nat/__tests__/nat-service.test.ts +305 -0
- package/src/nat/__tests__/nat-store.test.ts +93 -0
- package/src/nat/__tests__/port-mapper.test.ts +191 -0
- package/src/nat/__tests__/reachability.test.ts +24 -0
- package/src/nat/__tests__/secrets.test.ts +156 -0
- package/src/nat/address-resolver.ts +51 -0
- package/src/nat/ddns/duckdns.ts +76 -0
- package/src/nat/ddns/index.ts +55 -0
- package/src/nat/ddns/updater.ts +243 -0
- package/src/nat/external-ip.ts +154 -0
- package/src/nat/index.ts +71 -0
- package/src/nat/nat-service.ts +375 -0
- package/src/nat/nat-store.ts +154 -0
- package/src/nat/port-mapper.ts +329 -0
- package/src/nat/reachability.ts +34 -0
- package/src/nat/secrets/file-store.ts +130 -0
- package/src/nat/secrets/index.ts +82 -0
- package/src/nat/secrets/keytar-store.ts +67 -0
- package/src/nat/types.ts +157 -0
- package/src/orchestrator/host-process-orchestrator.ts +626 -0
- package/src/orchestrator/index.ts +3 -0
- package/src/orchestrator/log-rotator.ts +53 -0
- package/src/orchestrator/pid-liveness.ts +24 -0
- package/src/orchestrator/port-allocator.ts +43 -0
- package/src/orchestrator/state-store.ts +72 -0
- package/src/orchestrator/types.ts +95 -0
- package/src/server/__tests__/bus.test.ts +38 -0
- package/src/server/__tests__/error-handler.test.ts +96 -0
- package/src/server/__tests__/nodes-route.test.ts +179 -0
- package/src/server/__tests__/origin-guard.test.ts +72 -0
- package/src/server/__tests__/publishers.test.ts +216 -0
- package/src/server/__tests__/server.smoke.test.ts +178 -0
- package/src/server/__tests__/settings-route.test.ts +177 -0
- package/src/server/__tests__/sse-route.test.ts +119 -0
- package/src/server/__tests__/status-route.test.ts +113 -0
- package/src/server/error-handler.ts +108 -0
- package/src/server/events/bus.ts +42 -0
- package/src/server/events/sse-route.ts +81 -0
- package/src/server/events/types.ts +22 -0
- package/src/server/index.ts +209 -0
- package/src/server/origin-guard.ts +84 -0
- package/src/server/routes/nat.ts +63 -0
- package/src/server/routes/nodes.ts +147 -0
- package/src/server/routes/settings.ts +115 -0
- package/src/server/routes/status.ts +108 -0
- package/src/server/routes/trust-circle.ts +49 -0
- package/src/server/routes/update.ts +43 -0
- package/src/server/server.ts +122 -0
- package/src/server/settings-store.ts +37 -0
- package/src/server/static.ts +191 -0
- package/src/update/__tests__/apply.test.ts +101 -0
- package/src/update/__tests__/manifest.test.ts +93 -0
- package/src/update/__tests__/store.test.ts +81 -0
- package/src/update/__tests__/update-service.integration.test.ts +221 -0
- package/src/update/__tests__/version.test.ts +31 -0
- package/src/update/apply.ts +148 -0
- package/src/update/index.ts +303 -0
- package/src/update/manifest.ts +172 -0
- package/src/update/release-key.ts +55 -0
- package/src/update/store.ts +101 -0
- package/src/update/types.ts +109 -0
- package/src/update/version.ts +80 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { NatError, } from './types.js';
|
|
3
|
+
import { NatStore } from './nat-store.js';
|
|
4
|
+
import { ExternalIpDetector } from './external-ip.js';
|
|
5
|
+
import { PortMapperService, createDefaultPortMapper, } from './port-mapper.js';
|
|
6
|
+
import { evaluateReachability } from './reachability.js';
|
|
7
|
+
import { DdnsUpdater } from './ddns/updater.js';
|
|
8
|
+
import { getProvider, listProviders } from './ddns/index.js';
|
|
9
|
+
import { buildInviteAddresses, } from './address-resolver.js';
|
|
10
|
+
import { createSecretsStore, ddnsAccount } from './secrets/index.js';
|
|
11
|
+
const log = debug('cadre:host:nat-service');
|
|
12
|
+
/**
|
|
13
|
+
* NatService — orchestrates port mapping, external-IP detection, DDNS updates,
|
|
14
|
+
* and address resolution for invites.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle:
|
|
17
|
+
* - `start()`: load settings, install port mapping (if upnpEnabled), detect
|
|
18
|
+
* external IP, kick off DDNS loop.
|
|
19
|
+
* - `stop()`: release port mapping, stop DDNS timer.
|
|
20
|
+
* - `getStatus()`: build a fresh status snapshot from internal state (cheap).
|
|
21
|
+
* - `testReachability()`: re-run the IP + verify cycle and the heuristic.
|
|
22
|
+
* - `getInviteAddresses()`: build the multiaddrs to embed in invites.
|
|
23
|
+
*
|
|
24
|
+
* NatService is library + lifecycle only. The long-running process that owns
|
|
25
|
+
* it is `cadre-host-local-ui` (see docs/cadre-host.md and the ticket header).
|
|
26
|
+
*/
|
|
27
|
+
export class NatService {
|
|
28
|
+
constructor(opts) {
|
|
29
|
+
// Built once start() completes.
|
|
30
|
+
this.portService = null;
|
|
31
|
+
this.ddnsUpdater = null;
|
|
32
|
+
this.latestIp = null;
|
|
33
|
+
this.lastTestedAt = null;
|
|
34
|
+
this.started = false;
|
|
35
|
+
this.cadreNode = opts.cadreNode;
|
|
36
|
+
this.store = opts.store ?? new NatStore(opts.rootDir);
|
|
37
|
+
this.nowFn = opts.now ?? (() => new Date());
|
|
38
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
39
|
+
this.secretsStore = opts.secretsStore ?? null;
|
|
40
|
+
this.portMapper = opts.portMapper ?? null;
|
|
41
|
+
this.injectedDetector = opts.externalIpDetector ?? null;
|
|
42
|
+
this.secretsRootDir = opts.rootDir;
|
|
43
|
+
this.currentSettings = this.store.load();
|
|
44
|
+
}
|
|
45
|
+
/** Initial start: port mapping → IP detect → DDNS. Idempotent. */
|
|
46
|
+
async start() {
|
|
47
|
+
if (this.started)
|
|
48
|
+
return;
|
|
49
|
+
this.started = true;
|
|
50
|
+
// 1. Secrets store (lazy if not injected).
|
|
51
|
+
if (!this.secretsStore) {
|
|
52
|
+
this.secretsStore = await createSecretsStore(this.secretsRootDir);
|
|
53
|
+
}
|
|
54
|
+
// 2. Port mapping.
|
|
55
|
+
await this.startPortService();
|
|
56
|
+
// 3. External IP detection.
|
|
57
|
+
await this.detectIp();
|
|
58
|
+
// 4. DDNS updater.
|
|
59
|
+
this.ddnsUpdater = new DdnsUpdater({
|
|
60
|
+
settings: this.currentSettings.ddns,
|
|
61
|
+
secrets: this.secretsStore,
|
|
62
|
+
getExternalIp: () => this.latestIp?.publicIp ?? this.latestIp?.routerIp ?? null,
|
|
63
|
+
fetch: this.fetchImpl,
|
|
64
|
+
now: this.nowFn,
|
|
65
|
+
});
|
|
66
|
+
await this.ddnsUpdater.start();
|
|
67
|
+
}
|
|
68
|
+
/** Release the port mapping and clear timers. Idempotent. */
|
|
69
|
+
async stop() {
|
|
70
|
+
if (!this.started)
|
|
71
|
+
return;
|
|
72
|
+
this.started = false;
|
|
73
|
+
try {
|
|
74
|
+
this.ddnsUpdater?.stop();
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
log('ddns stop failed: %s', err.message);
|
|
78
|
+
}
|
|
79
|
+
this.ddnsUpdater = null;
|
|
80
|
+
try {
|
|
81
|
+
await this.portService?.stop();
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
log('port-service stop failed: %s', err.message);
|
|
85
|
+
}
|
|
86
|
+
this.portService = null;
|
|
87
|
+
}
|
|
88
|
+
/** Cheap read of the current snapshot. */
|
|
89
|
+
getStatus() {
|
|
90
|
+
const portState = this.portService?.getState() ?? this.disabledPortState();
|
|
91
|
+
const externalIp = this.latestIp?.publicIp ?? this.latestIp?.routerIp ?? null;
|
|
92
|
+
const ddns = this.ddnsUpdater?.getStatus() ?? {
|
|
93
|
+
providerId: this.currentSettings.ddns.providerId,
|
|
94
|
+
hostname: this.currentSettings.ddns.hostname,
|
|
95
|
+
externallyManaged: this.currentSettings.ddns.externallyManaged,
|
|
96
|
+
lastUpdateAt: null,
|
|
97
|
+
lastUpdateOk: null,
|
|
98
|
+
lastError: null,
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
portMode: this.currentSettings.upnpEnabled ? portState.mode : 'disabled',
|
|
102
|
+
externalPort: portState.externalPort,
|
|
103
|
+
internalPort: portState.internalPort,
|
|
104
|
+
routerExternalIp: portState.routerExternalIp,
|
|
105
|
+
mappingLeaseExpiresAt: portState.leaseExpiresAt?.toISOString() ?? null,
|
|
106
|
+
externalIp,
|
|
107
|
+
externalIpDetectedAt: this.latestIp?.detectedAt.toISOString() ?? null,
|
|
108
|
+
cgnatDetected: this.latestIp?.cgnat ?? false,
|
|
109
|
+
directReachability: evaluateReachability({
|
|
110
|
+
portMode: portState.mode,
|
|
111
|
+
cgnatDetected: this.latestIp?.cgnat ?? false,
|
|
112
|
+
}),
|
|
113
|
+
lastTestedAt: this.lastTestedAt?.toISOString() ?? null,
|
|
114
|
+
ddns,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Re-run the IP probe + port-mapper verify, then return a fresh snapshot. */
|
|
118
|
+
async testReachability() {
|
|
119
|
+
if (this.portService) {
|
|
120
|
+
await this.portService.refresh().catch((err) => log('refresh err: %s', err.message));
|
|
121
|
+
}
|
|
122
|
+
await this.detectIp();
|
|
123
|
+
this.lastTestedAt = this.nowFn();
|
|
124
|
+
// Best-effort DDNS push when the IP just changed.
|
|
125
|
+
if (this.ddnsUpdater) {
|
|
126
|
+
await this.ddnsUpdater.forceUpdate().catch((err) => log('ddns forceUpdate err: %s', err.message));
|
|
127
|
+
}
|
|
128
|
+
return this.getStatus();
|
|
129
|
+
}
|
|
130
|
+
/** Build the multiaddrs to embed in invites. */
|
|
131
|
+
async getInviteAddresses() {
|
|
132
|
+
const peerId = this.cadreNode.getPeerId();
|
|
133
|
+
const status = this.getStatus();
|
|
134
|
+
const input = {
|
|
135
|
+
peerId,
|
|
136
|
+
externalPort: status.externalPort,
|
|
137
|
+
ddnsHostname: status.ddns.hostname,
|
|
138
|
+
externalIp: status.externalIp,
|
|
139
|
+
reachable: status.directReachability === 'reachable',
|
|
140
|
+
libp2pAddrs: this.cadreNode.getMultiaddrs(),
|
|
141
|
+
};
|
|
142
|
+
return buildInviteAddresses(input);
|
|
143
|
+
}
|
|
144
|
+
/** Current settings (cheap read; for handlers to surface to UI). */
|
|
145
|
+
getSettings() {
|
|
146
|
+
return this.currentSettings;
|
|
147
|
+
}
|
|
148
|
+
/** Replace settings; persists to disk and reconfigures the runtime. */
|
|
149
|
+
async putSettings(patch) {
|
|
150
|
+
const next = this.store.update(patch);
|
|
151
|
+
const upnpChanged = next.upnpEnabled !== this.currentSettings.upnpEnabled;
|
|
152
|
+
const portChanged = next.externalPort !== this.currentSettings.externalPort ||
|
|
153
|
+
next.internalPort !== this.currentSettings.internalPort;
|
|
154
|
+
const ddnsChanged = next.ddns.providerId !== this.currentSettings.ddns.providerId ||
|
|
155
|
+
next.ddns.hostname !== this.currentSettings.ddns.hostname ||
|
|
156
|
+
next.ddns.externallyManaged !== this.currentSettings.ddns.externallyManaged ||
|
|
157
|
+
next.ddns.intervalMs !== this.currentSettings.ddns.intervalMs;
|
|
158
|
+
this.currentSettings = next;
|
|
159
|
+
if (this.started) {
|
|
160
|
+
if (upnpChanged || portChanged) {
|
|
161
|
+
try {
|
|
162
|
+
await this.portService?.stop();
|
|
163
|
+
}
|
|
164
|
+
catch { /* ignore */ }
|
|
165
|
+
this.portService = null;
|
|
166
|
+
await this.startPortService();
|
|
167
|
+
}
|
|
168
|
+
if (ddnsChanged && this.ddnsUpdater) {
|
|
169
|
+
await this.ddnsUpdater.updateSettings(next.ddns);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return this.getStatus();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Set DDNS configuration and persist secret values into the SecretsStore.
|
|
176
|
+
* Throws if the provider is unknown or required secrets are missing.
|
|
177
|
+
*/
|
|
178
|
+
async putDdns(body) {
|
|
179
|
+
if (!body || typeof body.providerId !== 'string' || typeof body.hostname !== 'string') {
|
|
180
|
+
throw new NatError('invalid_config', 'providerId and hostname are required');
|
|
181
|
+
}
|
|
182
|
+
const provider = getProvider(body.providerId);
|
|
183
|
+
if (!this.secretsStore) {
|
|
184
|
+
throw new NatError('secrets_unavailable', 'secrets store not initialised; call start() first');
|
|
185
|
+
}
|
|
186
|
+
// Persist secrets.
|
|
187
|
+
for (const field of provider.configFields) {
|
|
188
|
+
if (!field.secret)
|
|
189
|
+
continue;
|
|
190
|
+
const value = body.config[field.key];
|
|
191
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
192
|
+
throw new NatError('ddns_credentials_missing', `Missing value for required secret field "${field.key}" of provider "${provider.id}"`);
|
|
193
|
+
}
|
|
194
|
+
await this.secretsStore.set(ddnsAccount(provider.id, field.key), value);
|
|
195
|
+
}
|
|
196
|
+
// Update nat.json.
|
|
197
|
+
const externallyManaged = body.externallyManaged ?? false;
|
|
198
|
+
return await this.putSettings({
|
|
199
|
+
ddns: {
|
|
200
|
+
providerId: provider.id,
|
|
201
|
+
hostname: body.hostname,
|
|
202
|
+
externallyManaged,
|
|
203
|
+
intervalMs: this.currentSettings.ddns.intervalMs,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** List available DDNS providers (UI listing for configuration). */
|
|
208
|
+
listDdnsProviders() {
|
|
209
|
+
return listProviders().map((p) => ({
|
|
210
|
+
id: p.id,
|
|
211
|
+
displayName: p.displayName,
|
|
212
|
+
configFields: p.configFields,
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
// --- private ---
|
|
216
|
+
async startPortService() {
|
|
217
|
+
if (!this.currentSettings.upnpEnabled) {
|
|
218
|
+
log('UPnP disabled by settings; skipping port mapping');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
let mapper = this.portMapper;
|
|
222
|
+
if (!mapper) {
|
|
223
|
+
try {
|
|
224
|
+
mapper = await createDefaultPortMapper();
|
|
225
|
+
this.portMapper = mapper;
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
log('default port mapper unavailable: %s', err.message);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
this.portService = new PortMapperService({
|
|
233
|
+
mapper,
|
|
234
|
+
externalPort: this.currentSettings.externalPort,
|
|
235
|
+
internalPort: this.currentSettings.internalPort,
|
|
236
|
+
now: this.nowFn,
|
|
237
|
+
});
|
|
238
|
+
await this.portService.start();
|
|
239
|
+
}
|
|
240
|
+
async detectIp() {
|
|
241
|
+
// If the caller injected a detector (typically a test stub), use it as-is —
|
|
242
|
+
// don't second-guess them by stitching in the port mapper's router probe.
|
|
243
|
+
const detector = this.injectedDetector ?? (this.portMapper
|
|
244
|
+
? new ExternalIpDetector({
|
|
245
|
+
fetch: this.fetchImpl,
|
|
246
|
+
now: this.nowFn,
|
|
247
|
+
routerProbe: async () => this.portMapper.externalIp(),
|
|
248
|
+
})
|
|
249
|
+
: new ExternalIpDetector({
|
|
250
|
+
fetch: this.fetchImpl,
|
|
251
|
+
now: this.nowFn,
|
|
252
|
+
}));
|
|
253
|
+
try {
|
|
254
|
+
this.latestIp = await detector.detect();
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
log('detectIp failed: %s', err.message);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
disabledPortState() {
|
|
261
|
+
return {
|
|
262
|
+
mode: this.currentSettings.upnpEnabled ? 'failed' : 'disabled',
|
|
263
|
+
externalPort: this.currentSettings.externalPort,
|
|
264
|
+
internalPort: this.currentSettings.internalPort,
|
|
265
|
+
routerExternalIp: null,
|
|
266
|
+
leaseExpiresAt: null,
|
|
267
|
+
lastError: null,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Wrap a NatService into the typed handler shape consumed by
|
|
273
|
+
* `cadre-host-local-ui`. Mirrors `createTrustCircleHandlers`.
|
|
274
|
+
*/
|
|
275
|
+
export function createNatHandlers(service) {
|
|
276
|
+
return {
|
|
277
|
+
async getStatus() { return service.getStatus(); },
|
|
278
|
+
async testReachability() { return await service.testReachability(); },
|
|
279
|
+
async listDdnsProviders() { return service.listDdnsProviders(); },
|
|
280
|
+
async putDdns(body) { return await service.putDdns(body); },
|
|
281
|
+
async putSettings(body) { return await service.putSettings(body); },
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=nat-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nat-service.js","sourceRoot":"","sources":["../../src/nat/nat-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type NatSettingsFile } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Atomic JSON store for `nat.json`. Mirrors `TrustCircleStore` — write to
|
|
4
|
+
* `<path>.tmp`, then rename.
|
|
5
|
+
*
|
|
6
|
+
* Concurrency assumption: only one cadre-host process owns a given rootDir.
|
|
7
|
+
*/
|
|
8
|
+
export declare class NatStore {
|
|
9
|
+
private readonly path;
|
|
10
|
+
private cache;
|
|
11
|
+
constructor(rootDir: string);
|
|
12
|
+
filePath(): string;
|
|
13
|
+
/** Load (with caching). A missing file returns defaults. */
|
|
14
|
+
load(): NatSettingsFile;
|
|
15
|
+
/** Persist current settings atomically. */
|
|
16
|
+
save(state: NatSettingsFile): void;
|
|
17
|
+
/** Patch and persist (validation lives here). */
|
|
18
|
+
update(patch: Partial<Omit<NatSettingsFile, 'version'>>): NatSettingsFile;
|
|
19
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import debug from 'debug';
|
|
4
|
+
import { NatError } from './types.js';
|
|
5
|
+
const log = debug('cadre:host:nat-store');
|
|
6
|
+
const FILE_VERSION = 1;
|
|
7
|
+
/** Default port (libp2p TCP default). */
|
|
8
|
+
const DEFAULT_PORT = 4001;
|
|
9
|
+
/** Default DDNS update interval (5 minutes). */
|
|
10
|
+
const DEFAULT_DDNS_INTERVAL_MS = 5 * 60 * 1000;
|
|
11
|
+
/**
|
|
12
|
+
* Atomic JSON store for `nat.json`. Mirrors `TrustCircleStore` — write to
|
|
13
|
+
* `<path>.tmp`, then rename.
|
|
14
|
+
*
|
|
15
|
+
* Concurrency assumption: only one cadre-host process owns a given rootDir.
|
|
16
|
+
*/
|
|
17
|
+
export class NatStore {
|
|
18
|
+
constructor(rootDir) {
|
|
19
|
+
this.cache = null;
|
|
20
|
+
mkdirSync(rootDir, { recursive: true });
|
|
21
|
+
this.path = join(rootDir, 'nat.json');
|
|
22
|
+
}
|
|
23
|
+
filePath() {
|
|
24
|
+
return this.path;
|
|
25
|
+
}
|
|
26
|
+
/** Load (with caching). A missing file returns defaults. */
|
|
27
|
+
load() {
|
|
28
|
+
if (this.cache)
|
|
29
|
+
return this.cache;
|
|
30
|
+
if (!existsSync(this.path)) {
|
|
31
|
+
this.cache = defaultSettings();
|
|
32
|
+
return this.cache;
|
|
33
|
+
}
|
|
34
|
+
let raw;
|
|
35
|
+
try {
|
|
36
|
+
raw = readFileSync(this.path, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
throw new NatError('storage_error', `failed to read nat file at ${this.path}: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new NatError('storage_error', `nat file at ${this.path} is not valid JSON: ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
if (!isNatSettingsFile(parsed)) {
|
|
49
|
+
throw new NatError('storage_error', `nat file at ${this.path} has unexpected shape`);
|
|
50
|
+
}
|
|
51
|
+
this.cache = parsed;
|
|
52
|
+
return this.cache;
|
|
53
|
+
}
|
|
54
|
+
/** Persist current settings atomically. */
|
|
55
|
+
save(state) {
|
|
56
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
57
|
+
const next = { ...state, version: FILE_VERSION };
|
|
58
|
+
const payload = JSON.stringify(next, null, 2);
|
|
59
|
+
const tmp = `${this.path}.tmp`;
|
|
60
|
+
writeFileSync(tmp, payload, { encoding: 'utf8' });
|
|
61
|
+
renameSync(tmp, this.path);
|
|
62
|
+
this.cache = next;
|
|
63
|
+
log('saved nat.json (port=%d, ddns=%s) to %s', next.externalPort, next.ddns.providerId ?? '(none)', this.path);
|
|
64
|
+
}
|
|
65
|
+
/** Patch and persist (validation lives here). */
|
|
66
|
+
update(patch) {
|
|
67
|
+
const current = this.load();
|
|
68
|
+
const merged = {
|
|
69
|
+
...current,
|
|
70
|
+
...patch,
|
|
71
|
+
ddns: { ...current.ddns, ...(patch.ddns ?? {}) },
|
|
72
|
+
version: FILE_VERSION,
|
|
73
|
+
};
|
|
74
|
+
validate(merged);
|
|
75
|
+
this.save(merged);
|
|
76
|
+
return merged;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function defaultSettings() {
|
|
80
|
+
return {
|
|
81
|
+
version: FILE_VERSION,
|
|
82
|
+
externalPort: DEFAULT_PORT,
|
|
83
|
+
internalPort: DEFAULT_PORT,
|
|
84
|
+
upnpEnabled: true,
|
|
85
|
+
ddns: defaultDdns(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function defaultDdns() {
|
|
89
|
+
return {
|
|
90
|
+
providerId: null,
|
|
91
|
+
hostname: null,
|
|
92
|
+
externallyManaged: false,
|
|
93
|
+
intervalMs: DEFAULT_DDNS_INTERVAL_MS,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function isNatSettingsFile(v) {
|
|
97
|
+
if (!v || typeof v !== 'object')
|
|
98
|
+
return false;
|
|
99
|
+
const obj = v;
|
|
100
|
+
if (obj.version !== FILE_VERSION)
|
|
101
|
+
return false;
|
|
102
|
+
if (typeof obj.externalPort !== 'number' || typeof obj.internalPort !== 'number')
|
|
103
|
+
return false;
|
|
104
|
+
if (typeof obj.upnpEnabled !== 'boolean')
|
|
105
|
+
return false;
|
|
106
|
+
const ddns = obj.ddns;
|
|
107
|
+
if (!ddns || typeof ddns !== 'object')
|
|
108
|
+
return false;
|
|
109
|
+
if (ddns.providerId !== null && typeof ddns.providerId !== 'string')
|
|
110
|
+
return false;
|
|
111
|
+
if (ddns.hostname !== null && typeof ddns.hostname !== 'string')
|
|
112
|
+
return false;
|
|
113
|
+
if (typeof ddns.externallyManaged !== 'boolean')
|
|
114
|
+
return false;
|
|
115
|
+
if (typeof ddns.intervalMs !== 'number')
|
|
116
|
+
return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
function validate(s) {
|
|
120
|
+
if (!Number.isInteger(s.externalPort) || s.externalPort < 1 || s.externalPort > 65535) {
|
|
121
|
+
throw new NatError('invalid_config', `externalPort must be 1-65535, got ${s.externalPort}`);
|
|
122
|
+
}
|
|
123
|
+
if (!Number.isInteger(s.internalPort) || s.internalPort < 1 || s.internalPort > 65535) {
|
|
124
|
+
throw new NatError('invalid_config', `internalPort must be 1-65535, got ${s.internalPort}`);
|
|
125
|
+
}
|
|
126
|
+
if (!Number.isFinite(s.ddns.intervalMs) || s.ddns.intervalMs < 1000) {
|
|
127
|
+
throw new NatError('invalid_config', `ddns.intervalMs must be >= 1000ms, got ${s.ddns.intervalMs}`);
|
|
128
|
+
}
|
|
129
|
+
if (s.ddns.providerId !== null && s.ddns.providerId.length === 0) {
|
|
130
|
+
throw new NatError('invalid_config', 'ddns.providerId must be a non-empty string or null');
|
|
131
|
+
}
|
|
132
|
+
if (s.ddns.hostname !== null && s.ddns.hostname.length === 0) {
|
|
133
|
+
throw new NatError('invalid_config', 'ddns.hostname must be a non-empty string or null');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=nat-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nat-store.js","sourceRoot":"","sources":["../../src/nat/nat-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type PortForwardMode } from './types.js';
|
|
2
|
+
/** Default lease TTL: 1 hour. The router must keep our mapping at least this long. */
|
|
3
|
+
export declare const DEFAULT_LEASE_TTL_MS: number;
|
|
4
|
+
/** Default refresh cadence: half the TTL, so two refreshes per lease. */
|
|
5
|
+
export declare const DEFAULT_REFRESH_MS: number;
|
|
6
|
+
/** Result of a successful map(). */
|
|
7
|
+
export interface PortMappingResult {
|
|
8
|
+
mode: 'auto-upnp' | 'auto-natpmp';
|
|
9
|
+
externalIp: string | null;
|
|
10
|
+
externalPort: number;
|
|
11
|
+
internalPort: number;
|
|
12
|
+
protocol: 'tcp' | 'udp';
|
|
13
|
+
leaseExpiresAt: Date;
|
|
14
|
+
}
|
|
15
|
+
export interface PortMapOptions {
|
|
16
|
+
externalPort: number;
|
|
17
|
+
internalPort: number;
|
|
18
|
+
protocol?: 'tcp' | 'udp';
|
|
19
|
+
/** Lease TTL in milliseconds. Defaults to DEFAULT_LEASE_TTL_MS. */
|
|
20
|
+
ttlMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Wrapper around the underlying UPnP/NAT-PMP client. Abstract so tests can
|
|
24
|
+
* stub the network behavior without spinning up a real router.
|
|
25
|
+
*/
|
|
26
|
+
export interface PortMapper {
|
|
27
|
+
/** Attempt to install a port mapping. Throws NatError(mapping_failed) on failure. */
|
|
28
|
+
map(opts: PortMapOptions): Promise<PortMappingResult>;
|
|
29
|
+
/** Re-query the router; returns true if the mapping is still present. */
|
|
30
|
+
verify(externalPort: number, protocol?: 'tcp' | 'udp'): Promise<boolean>;
|
|
31
|
+
/** Query the router's WAN/external IP, if exposed. */
|
|
32
|
+
externalIp(): Promise<string | null>;
|
|
33
|
+
/** Remove a mapping installed by this mapper. */
|
|
34
|
+
unmap(externalPort: number, protocol?: 'tcp' | 'udp'): Promise<void>;
|
|
35
|
+
/** Release client resources (timers, sockets). Idempotent. */
|
|
36
|
+
stop(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/** Constructor for the default mapper. Loads `@achingbrain/nat-port-mapper` dynamically. */
|
|
39
|
+
export type PortMapperFactory = () => Promise<PortMapper>;
|
|
40
|
+
/**
|
|
41
|
+
* Default factory: loads @achingbrain/nat-port-mapper. Wrapped in dynamic
|
|
42
|
+
* import so a missing dep at install time doesn't crash the package.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createDefaultPortMapper(opts?: {
|
|
45
|
+
description?: string;
|
|
46
|
+
ttlMs?: number;
|
|
47
|
+
}): Promise<PortMapper>;
|
|
48
|
+
/** Public-facing port-mapper service: wraps a PortMapper with refresh + state. */
|
|
49
|
+
export interface PortMapperServiceOptions {
|
|
50
|
+
mapper: PortMapper;
|
|
51
|
+
externalPort: number;
|
|
52
|
+
internalPort: number;
|
|
53
|
+
protocol?: 'tcp' | 'udp';
|
|
54
|
+
ttlMs?: number;
|
|
55
|
+
refreshMs?: number;
|
|
56
|
+
/** Clock override for tests. */
|
|
57
|
+
now?: () => Date;
|
|
58
|
+
/** Timer overrides for tests (must return a handle usable by `clear`). */
|
|
59
|
+
setTimer?: (fn: () => void, ms: number) => NodeJS.Timeout;
|
|
60
|
+
clearTimer?: (h: NodeJS.Timeout) => void;
|
|
61
|
+
}
|
|
62
|
+
/** State snapshot returned by `PortMapperService.getStatus()`. */
|
|
63
|
+
export interface PortMapperState {
|
|
64
|
+
mode: PortForwardMode;
|
|
65
|
+
externalPort: number;
|
|
66
|
+
internalPort: number;
|
|
67
|
+
routerExternalIp: string | null;
|
|
68
|
+
leaseExpiresAt: Date | null;
|
|
69
|
+
lastError: string | null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Long-running orchestrator over a PortMapper. Owns the refresh timer and
|
|
73
|
+
* surfaces the latest state for the NatService snapshot.
|
|
74
|
+
*/
|
|
75
|
+
export declare class PortMapperService {
|
|
76
|
+
private readonly mapper;
|
|
77
|
+
private readonly externalPort;
|
|
78
|
+
private readonly internalPort;
|
|
79
|
+
private readonly protocol;
|
|
80
|
+
private readonly ttlMs;
|
|
81
|
+
private readonly refreshMs;
|
|
82
|
+
private readonly nowFn;
|
|
83
|
+
private readonly setTimer;
|
|
84
|
+
private readonly clearTimer;
|
|
85
|
+
private state;
|
|
86
|
+
private refreshHandle;
|
|
87
|
+
private started;
|
|
88
|
+
constructor(opts: PortMapperServiceOptions);
|
|
89
|
+
getState(): PortMapperState;
|
|
90
|
+
/** Install the mapping and schedule periodic refresh. */
|
|
91
|
+
start(): Promise<void>;
|
|
92
|
+
/** Release the mapping and clear timers. Idempotent. */
|
|
93
|
+
stop(): Promise<void>;
|
|
94
|
+
/** Force an immediate verify+refresh cycle (used by testReachability). */
|
|
95
|
+
refresh(): Promise<PortMapperState>;
|
|
96
|
+
private scheduleRefresh;
|
|
97
|
+
private installMapping;
|
|
98
|
+
}
|