@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,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/settings — host-level settings UI surface.
|
|
3
|
+
*
|
|
4
|
+
* The persisted shape is `host.config.json` (managed by the installer).
|
|
5
|
+
* This route is a thin passthrough plus a write-whitelist:
|
|
6
|
+
*
|
|
7
|
+
* - uiPort rejected (restart required; only edit at install time)
|
|
8
|
+
* - libp2pPort rejected (restart required)
|
|
9
|
+
* - upnpEnabled accepted → also propagated to NatService.putSettings
|
|
10
|
+
* - updates.autoApply accepted → propagated to UpdateService.putSettings
|
|
11
|
+
* - updates.manifestUrl accepted → propagated to UpdateService.putSettings
|
|
12
|
+
* - anything else 400 invalid_setting
|
|
13
|
+
*
|
|
14
|
+
* GET returns the full `HostConfigFile` (the SPA wants the full shape so it
|
|
15
|
+
* can render a read-only view of the rejected keys).
|
|
16
|
+
*/
|
|
17
|
+
const FORBIDDEN_KEYS = new Set(['uiPort', 'libp2pPort', 'dataDir', 'identityPath', 'installId', 'installedAt', 'installerVersion', 'version']);
|
|
18
|
+
const WRITABLE_TOP_KEYS = new Set(['upnpEnabled', 'updates']);
|
|
19
|
+
const WRITABLE_UPDATES_KEYS = new Set(['autoApply', 'manifestUrl']);
|
|
20
|
+
export function registerSettingsRoutes(app, opts) {
|
|
21
|
+
const { settingsStore, nat, update } = opts;
|
|
22
|
+
app.get('/api/settings', async () => {
|
|
23
|
+
return { ok: true, data: settingsStore.read() };
|
|
24
|
+
});
|
|
25
|
+
app.put('/api/settings', async (request, reply) => {
|
|
26
|
+
const body = (request.body ?? {});
|
|
27
|
+
const validation = validatePatch(body);
|
|
28
|
+
if (validation.error) {
|
|
29
|
+
return reply.code(400).send({
|
|
30
|
+
ok: false,
|
|
31
|
+
error: { code: 'invalid_setting', message: validation.error },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const current = settingsStore.read();
|
|
35
|
+
const patch = {};
|
|
36
|
+
if ('upnpEnabled' in body) {
|
|
37
|
+
patch.upnpEnabled = body.upnpEnabled;
|
|
38
|
+
// Propagate UPnP toggle into the running NatService so the change
|
|
39
|
+
// takes effect immediately. NatService persists into nat.json — that's
|
|
40
|
+
// independent of host.config.json's `upnpEnabled` (which the installer
|
|
41
|
+
// uses on first run to seed nat.json). Update both for consistency.
|
|
42
|
+
await nat.putSettings({ upnpEnabled: body.upnpEnabled });
|
|
43
|
+
}
|
|
44
|
+
if ('updates' in body) {
|
|
45
|
+
const updates = body.updates;
|
|
46
|
+
const nextUpdates = { ...current.updates };
|
|
47
|
+
if ('autoApply' in updates)
|
|
48
|
+
nextUpdates.autoApply = updates.autoApply;
|
|
49
|
+
if ('manifestUrl' in updates)
|
|
50
|
+
nextUpdates.manifestUrl = updates.manifestUrl;
|
|
51
|
+
patch.updates = nextUpdates;
|
|
52
|
+
if (update) {
|
|
53
|
+
const propagate = {};
|
|
54
|
+
if ('autoApply' in updates)
|
|
55
|
+
propagate.autoApply = updates.autoApply;
|
|
56
|
+
if ('manifestUrl' in updates)
|
|
57
|
+
propagate.manifestUrl = updates.manifestUrl;
|
|
58
|
+
await update.putSettings(propagate);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const next = settingsStore.update(patch);
|
|
62
|
+
return { ok: true, data: next };
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function validatePatch(body) {
|
|
66
|
+
for (const key of Object.keys(body)) {
|
|
67
|
+
if (FORBIDDEN_KEYS.has(key)) {
|
|
68
|
+
return { error: `Setting "${key}" is not editable at runtime; change it in host.config.json and restart cadre-host.` };
|
|
69
|
+
}
|
|
70
|
+
if (!WRITABLE_TOP_KEYS.has(key)) {
|
|
71
|
+
return { error: `Unknown setting "${key}"` };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if ('upnpEnabled' in body && typeof body.upnpEnabled !== 'boolean') {
|
|
75
|
+
return { error: '"upnpEnabled" must be a boolean' };
|
|
76
|
+
}
|
|
77
|
+
if ('updates' in body) {
|
|
78
|
+
if (!body.updates || typeof body.updates !== 'object') {
|
|
79
|
+
return { error: '"updates" must be an object' };
|
|
80
|
+
}
|
|
81
|
+
const updates = body.updates;
|
|
82
|
+
for (const key of Object.keys(updates)) {
|
|
83
|
+
if (!WRITABLE_UPDATES_KEYS.has(key)) {
|
|
84
|
+
return { error: `Unknown setting "updates.${key}"` };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if ('autoApply' in updates && typeof updates.autoApply !== 'boolean') {
|
|
88
|
+
return { error: '"updates.autoApply" must be a boolean' };
|
|
89
|
+
}
|
|
90
|
+
if ('manifestUrl' in updates &&
|
|
91
|
+
typeof updates.manifestUrl !== 'string') {
|
|
92
|
+
return { error: '"updates.manifestUrl" must be a string' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/server/routes/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/status — aggregated health snapshot used by the SPA's dashboard.
|
|
3
|
+
*
|
|
4
|
+
* All inputs are in-memory subsystem state — no disk reads on each call.
|
|
5
|
+
* The response budget is < 5 ms; favour `getStatus()` over `testReachability()`.
|
|
6
|
+
*/
|
|
7
|
+
import type { FastifyInstance } from 'fastify';
|
|
8
|
+
import type { HostProcessOrchestrator } from '../../orchestrator/index.js';
|
|
9
|
+
import type { ManagedNodeInfo } from '../../orchestrator/types.js';
|
|
10
|
+
import type { TrustCircleService } from '../../auth/index.js';
|
|
11
|
+
import type { NatService } from '../../nat/index.js';
|
|
12
|
+
import type { NatStatusSnapshot } from '../../nat/types.js';
|
|
13
|
+
import type { UpdateService } from '../../update/index.js';
|
|
14
|
+
export interface StatusRouteOptions {
|
|
15
|
+
orchestrator: HostProcessOrchestrator;
|
|
16
|
+
trustCircle: TrustCircleService;
|
|
17
|
+
nat: NatService;
|
|
18
|
+
update?: UpdateService;
|
|
19
|
+
}
|
|
20
|
+
export interface StatusResponse {
|
|
21
|
+
service: {
|
|
22
|
+
name: 'cadre-host';
|
|
23
|
+
version: string;
|
|
24
|
+
uptimeSeconds: number;
|
|
25
|
+
};
|
|
26
|
+
nodes: Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
partyId: string;
|
|
29
|
+
status: ManagedNodeInfo['status'];
|
|
30
|
+
profile: ManagedNodeInfo['profile'];
|
|
31
|
+
}>;
|
|
32
|
+
trustCircle: {
|
|
33
|
+
members: number;
|
|
34
|
+
pending: number;
|
|
35
|
+
};
|
|
36
|
+
connectivity: NatStatusSnapshot;
|
|
37
|
+
update?: {
|
|
38
|
+
available?: string;
|
|
39
|
+
lastChecked?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function registerStatusRoute(app: FastifyInstance, opts: StatusRouteOptions): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/status — aggregated health snapshot used by the SPA's dashboard.
|
|
3
|
+
*
|
|
4
|
+
* All inputs are in-memory subsystem state — no disk reads on each call.
|
|
5
|
+
* The response budget is < 5 ms; favour `getStatus()` over `testReachability()`.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
const PROCESS_STARTED_AT = process.hrtime.bigint();
|
|
11
|
+
export function registerStatusRoute(app, opts) {
|
|
12
|
+
const version = readPackageVersion();
|
|
13
|
+
app.get('/api/status', async () => {
|
|
14
|
+
const nodes = opts.orchestrator.listNodes().map((n) => ({
|
|
15
|
+
id: n.id,
|
|
16
|
+
partyId: n.partyId,
|
|
17
|
+
status: n.status,
|
|
18
|
+
profile: n.profile,
|
|
19
|
+
}));
|
|
20
|
+
const tc = await opts.trustCircle.list();
|
|
21
|
+
const connectivity = opts.nat.getStatus();
|
|
22
|
+
const response = {
|
|
23
|
+
service: {
|
|
24
|
+
name: 'cadre-host',
|
|
25
|
+
version,
|
|
26
|
+
uptimeSeconds: secondsSince(PROCESS_STARTED_AT),
|
|
27
|
+
},
|
|
28
|
+
nodes,
|
|
29
|
+
trustCircle: { members: tc.members.length, pending: tc.pending.length },
|
|
30
|
+
connectivity,
|
|
31
|
+
};
|
|
32
|
+
if (opts.update) {
|
|
33
|
+
const state = await opts.update.getState();
|
|
34
|
+
const update = {};
|
|
35
|
+
if (state.available?.version)
|
|
36
|
+
update.available = state.available.version;
|
|
37
|
+
if (state.lastChecked)
|
|
38
|
+
update.lastChecked = state.lastChecked;
|
|
39
|
+
response.update = update;
|
|
40
|
+
}
|
|
41
|
+
return response;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function secondsSince(start) {
|
|
45
|
+
const now = process.hrtime.bigint();
|
|
46
|
+
const elapsedNs = now - start;
|
|
47
|
+
return Number(elapsedNs / 1000000000n);
|
|
48
|
+
}
|
|
49
|
+
let cachedVersion = null;
|
|
50
|
+
function readPackageVersion() {
|
|
51
|
+
if (cachedVersion)
|
|
52
|
+
return cachedVersion;
|
|
53
|
+
try {
|
|
54
|
+
// dist/server/routes/status.js -> ../../../package.json
|
|
55
|
+
// src/server/routes/status.ts -> ../../../package.json (vitest)
|
|
56
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
57
|
+
const candidates = [
|
|
58
|
+
resolve(here, '..', '..', '..', 'package.json'),
|
|
59
|
+
resolve(here, '..', '..', 'package.json'),
|
|
60
|
+
];
|
|
61
|
+
for (const p of candidates) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
64
|
+
if (parsed.version) {
|
|
65
|
+
cachedVersion = parsed.version;
|
|
66
|
+
return cachedVersion;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch { /* try next */ }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { /* ignore */ }
|
|
73
|
+
cachedVersion = '0.0.0-unknown';
|
|
74
|
+
return cachedVersion;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/server/routes/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust-circle HTTP routes — thin adapters around `TrustCircleHandlers`
|
|
3
|
+
* (`createTrustCircleHandlers(service)`). Side-effects (invite issued,
|
|
4
|
+
* pending revoked, member removed) emit a `trust-circle-changed` bus
|
|
5
|
+
* event so the SPA can refresh without polling.
|
|
6
|
+
*
|
|
7
|
+
* Mount path: `/auth/*` — the existing CLI (cadre-host invite / trust)
|
|
8
|
+
* already targets these URLs; do not change them.
|
|
9
|
+
*/
|
|
10
|
+
import type { FastifyInstance } from 'fastify';
|
|
11
|
+
import type { TrustCircleHandlers } from '../../auth/types.js';
|
|
12
|
+
import type { EventBus } from '../events/bus.js';
|
|
13
|
+
export interface TrustCircleRoutesOptions {
|
|
14
|
+
handlers: TrustCircleHandlers;
|
|
15
|
+
events: EventBus;
|
|
16
|
+
}
|
|
17
|
+
export declare function registerTrustCircleRoutes(app: FastifyInstance, opts: TrustCircleRoutesOptions): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust-circle HTTP routes — thin adapters around `TrustCircleHandlers`
|
|
3
|
+
* (`createTrustCircleHandlers(service)`). Side-effects (invite issued,
|
|
4
|
+
* pending revoked, member removed) emit a `trust-circle-changed` bus
|
|
5
|
+
* event so the SPA can refresh without polling.
|
|
6
|
+
*
|
|
7
|
+
* Mount path: `/auth/*` — the existing CLI (cadre-host invite / trust)
|
|
8
|
+
* already targets these URLs; do not change them.
|
|
9
|
+
*/
|
|
10
|
+
export function registerTrustCircleRoutes(app, opts) {
|
|
11
|
+
const { handlers, events } = opts;
|
|
12
|
+
app.get('/auth/trust-circle', async () => {
|
|
13
|
+
return handlers.listTrustCircle();
|
|
14
|
+
});
|
|
15
|
+
app.post('/auth/invites', async (request) => {
|
|
16
|
+
const body = (request.body ?? {});
|
|
17
|
+
const label = typeof body.label === 'string' ? body.label : '';
|
|
18
|
+
const ttlMs = typeof body.ttlMs === 'number' ? body.ttlMs : undefined;
|
|
19
|
+
const args = ttlMs !== undefined ? { label, ttlMs } : { label };
|
|
20
|
+
const result = await handlers.postInvite(args);
|
|
21
|
+
events.publish({ type: 'trust-circle-changed', kind: 'invited' });
|
|
22
|
+
return result;
|
|
23
|
+
});
|
|
24
|
+
app.delete('/auth/invites/:token', async (request) => {
|
|
25
|
+
await handlers.deleteInvite(request.params.token);
|
|
26
|
+
events.publish({ type: 'trust-circle-changed', kind: 'revoked' });
|
|
27
|
+
return { ok: true };
|
|
28
|
+
});
|
|
29
|
+
app.delete('/auth/members/:peerId', async (request) => {
|
|
30
|
+
await handlers.deleteMember(request.params.peerId);
|
|
31
|
+
events.publish({ type: 'trust-circle-changed', kind: 'revoked' });
|
|
32
|
+
return { ok: true };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=trust-circle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-circle.js","sourceRoot":"","sources":["../../../src/server/routes/trust-circle.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,MAAM,UAAU,yBAAyB,CAAC,GAAoB,EAAE,IAA8B;IAC5F,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAElC,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QACvC,OAAO,QAAQ,CAAC,eAAe,EAAE,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC1C,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAyC,CAAC;QAC1E,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAClE,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAgC,sBAAsB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAClF,MAAM,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAClE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAiC,uBAAuB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACpF,MAAM,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAClE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update-flow HTTP routes — thin adapters around `UpdateHandlers`
|
|
3
|
+
* (`createUpdateHandlers(service)`). The /update/apply route publishes
|
|
4
|
+
* `update-available` for the post-apply state when the new version
|
|
5
|
+
* differs from the running one.
|
|
6
|
+
*
|
|
7
|
+
* Mount path: `/update/*` (matches docs/cadre-host.md).
|
|
8
|
+
*/
|
|
9
|
+
import type { FastifyInstance } from 'fastify';
|
|
10
|
+
import type { UpdateHandlers } from '../../update/index.js';
|
|
11
|
+
import type { EventBus } from '../events/bus.js';
|
|
12
|
+
export interface UpdateRoutesOptions {
|
|
13
|
+
handlers: UpdateHandlers;
|
|
14
|
+
events: EventBus;
|
|
15
|
+
}
|
|
16
|
+
export declare function registerUpdateRoutes(app: FastifyInstance, opts: UpdateRoutesOptions): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update-flow HTTP routes — thin adapters around `UpdateHandlers`
|
|
3
|
+
* (`createUpdateHandlers(service)`). The /update/apply route publishes
|
|
4
|
+
* `update-available` for the post-apply state when the new version
|
|
5
|
+
* differs from the running one.
|
|
6
|
+
*
|
|
7
|
+
* Mount path: `/update/*` (matches docs/cadre-host.md).
|
|
8
|
+
*/
|
|
9
|
+
export function registerUpdateRoutes(app, opts) {
|
|
10
|
+
const { handlers, events } = opts;
|
|
11
|
+
app.get('/update', async () => handlers.getState());
|
|
12
|
+
app.post('/update/apply', async () => {
|
|
13
|
+
const result = await handlers.postApply();
|
|
14
|
+
// Apply succeeded — the still-running binary is the *old* one; the new
|
|
15
|
+
// one becomes live after the service-host restarts. Surface the version
|
|
16
|
+
// we just applied so the UI can prompt the user to refresh.
|
|
17
|
+
events.publish({
|
|
18
|
+
type: 'update-available',
|
|
19
|
+
version: result.toVersion,
|
|
20
|
+
});
|
|
21
|
+
return result;
|
|
22
|
+
});
|
|
23
|
+
app.get('/update/settings', async () => handlers.getSettings());
|
|
24
|
+
app.put('/update/settings', async (request) => {
|
|
25
|
+
const body = (request.body ?? {});
|
|
26
|
+
return handlers.putSettings(body);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../../src/server/routes/update.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,MAAM,UAAU,oBAAoB,CAAC,GAAoB,EAAE,IAAyB;IAClF,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAElC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEpD,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC1C,uEAAuE;QACvE,wEAAwE;QACxE,4DAA4D;QAC5D,MAAM,CAAC,OAAO,CAAC;YACb,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,MAAM,CAAC,SAAS;SAC1B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IAEhE,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC5C,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAkD,CAAC;QACnF,OAAO,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fastify lifecycle for the cadre-host local UI.
|
|
3
|
+
*
|
|
4
|
+
* Bound to `127.0.0.1:<uiPort>` only — never `0.0.0.0`. On `EADDRINUSE`
|
|
5
|
+
* tries the configured port and then nine consecutive ports. On total
|
|
6
|
+
* failure the start() promise rejects with a clear message naming every
|
|
7
|
+
* port that was attempted.
|
|
8
|
+
*
|
|
9
|
+
* Shutdown drains with a 5 s timeout, mirroring cadre-provider's pattern.
|
|
10
|
+
*/
|
|
11
|
+
import { type FastifyInstance } from 'fastify';
|
|
12
|
+
export interface StartServerOptions {
|
|
13
|
+
/** Preferred port. The server tries this then the next 9 on EADDRINUSE. */
|
|
14
|
+
preferredPort: number;
|
|
15
|
+
/**
|
|
16
|
+
* Test override: when 0, lets the OS pick any free port (skips the
|
|
17
|
+
* fallback loop). The returned `port` reflects the actual bound port.
|
|
18
|
+
*/
|
|
19
|
+
forcePort?: 0;
|
|
20
|
+
}
|
|
21
|
+
export interface StartedServer {
|
|
22
|
+
port: number;
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ShutdownOptions {
|
|
26
|
+
/** Override the 5 s drain timeout (tests). */
|
|
27
|
+
drainTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Bind `app` to the loopback interface with port-fallback. Returns the
|
|
31
|
+
* actually-bound port — the configured port may be in use.
|
|
32
|
+
*/
|
|
33
|
+
export declare function startListening(app: FastifyInstance, opts: StartServerOptions): Promise<StartedServer>;
|
|
34
|
+
/**
|
|
35
|
+
* Close the server with a hard 5 s drain timeout. Idempotent — safe to call
|
|
36
|
+
* from a SIGINT/SIGTERM handler that may fire twice.
|
|
37
|
+
*/
|
|
38
|
+
export declare function stopListening(app: FastifyInstance, opts?: ShutdownOptions): Promise<void>;
|
|
39
|
+
/** Create a bare Fastify instance with the project's logging convention. */
|
|
40
|
+
export declare function buildFastify(): FastifyInstance;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fastify lifecycle for the cadre-host local UI.
|
|
3
|
+
*
|
|
4
|
+
* Bound to `127.0.0.1:<uiPort>` only — never `0.0.0.0`. On `EADDRINUSE`
|
|
5
|
+
* tries the configured port and then nine consecutive ports. On total
|
|
6
|
+
* failure the start() promise rejects with a clear message naming every
|
|
7
|
+
* port that was attempted.
|
|
8
|
+
*
|
|
9
|
+
* Shutdown drains with a 5 s timeout, mirroring cadre-provider's pattern.
|
|
10
|
+
*/
|
|
11
|
+
import Fastify from 'fastify';
|
|
12
|
+
import debug from 'debug';
|
|
13
|
+
const log = debug('cadre:host:server');
|
|
14
|
+
/** Hard cap on Fastify's drain phase during shutdown. */
|
|
15
|
+
const SHUTDOWN_DRAIN_TIMEOUT_MS = 5000;
|
|
16
|
+
/** Inclusive count of ports to try (config port + 9 alternates). */
|
|
17
|
+
const PORT_FALLBACK_TRIES = 10;
|
|
18
|
+
/** Loopback interface; never serve from 0.0.0.0. */
|
|
19
|
+
const LOOPBACK_HOST = '127.0.0.1';
|
|
20
|
+
/**
|
|
21
|
+
* Bind `app` to the loopback interface with port-fallback. Returns the
|
|
22
|
+
* actually-bound port — the configured port may be in use.
|
|
23
|
+
*/
|
|
24
|
+
export async function startListening(app, opts) {
|
|
25
|
+
if (opts.forcePort === 0) {
|
|
26
|
+
const address = await app.listen({ host: LOOPBACK_HOST, port: 0 });
|
|
27
|
+
const port = parsePort(address);
|
|
28
|
+
return { port, url: `http://${LOOPBACK_HOST}:${port}` };
|
|
29
|
+
}
|
|
30
|
+
const tried = [];
|
|
31
|
+
let lastErr = null;
|
|
32
|
+
for (let i = 0; i < PORT_FALLBACK_TRIES; i++) {
|
|
33
|
+
const port = opts.preferredPort + i;
|
|
34
|
+
tried.push(port);
|
|
35
|
+
try {
|
|
36
|
+
const address = await app.listen({ host: LOOPBACK_HOST, port });
|
|
37
|
+
const bound = parsePort(address);
|
|
38
|
+
if (i > 0) {
|
|
39
|
+
log('port %d in use; bound on fallback port %d', opts.preferredPort, bound);
|
|
40
|
+
}
|
|
41
|
+
return { port: bound, url: `http://${LOOPBACK_HOST}:${bound}` };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const code = err.code;
|
|
45
|
+
if (code !== 'EADDRINUSE') {
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
lastErr = err;
|
|
49
|
+
log('port %d in use, trying next', port);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`cadre-host: all ${PORT_FALLBACK_TRIES} candidate ports were in use ` +
|
|
53
|
+
`(${tried.join(', ')}). Reconfigure uiPort in host.config.json. ` +
|
|
54
|
+
`Last error: ${lastErr?.message ?? 'unknown'}`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Close the server with a hard 5 s drain timeout. Idempotent — safe to call
|
|
58
|
+
* from a SIGINT/SIGTERM handler that may fire twice.
|
|
59
|
+
*/
|
|
60
|
+
export async function stopListening(app, opts = {}) {
|
|
61
|
+
const drainMs = opts.drainTimeoutMs ?? SHUTDOWN_DRAIN_TIMEOUT_MS;
|
|
62
|
+
let timer;
|
|
63
|
+
const drain = app.close();
|
|
64
|
+
const timeout = new Promise((resolve) => {
|
|
65
|
+
timer = setTimeout(() => resolve('timeout'), drainMs);
|
|
66
|
+
});
|
|
67
|
+
const outcome = await Promise.race([drain.then(() => 'drained'), timeout]);
|
|
68
|
+
if (timer)
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
if (outcome === 'timeout') {
|
|
71
|
+
log('shutdown drain timeout after %dms', drainMs);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Create a bare Fastify instance with the project's logging convention. */
|
|
75
|
+
export function buildFastify() {
|
|
76
|
+
return Fastify({
|
|
77
|
+
// Stay quiet by default — request-level logs are noisy on a local UI.
|
|
78
|
+
// Operators can opt in via the `debug` env var (cadre:host:*).
|
|
79
|
+
logger: false,
|
|
80
|
+
disableRequestLogging: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function parsePort(address) {
|
|
84
|
+
// address is e.g. http://127.0.0.1:8765
|
|
85
|
+
const m = address.match(/:(\d+)\/?$/);
|
|
86
|
+
if (!m)
|
|
87
|
+
throw new Error(`cadre-host: unable to parse port from listen address: ${address}`);
|
|
88
|
+
return Number(m[1]);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings store — read-write facade around `host.config.json` for the
|
|
3
|
+
* /api/settings route.
|
|
4
|
+
*
|
|
5
|
+
* The installer (`6.4.1`) owns the schema (`HostConfigFile` v2). This module
|
|
6
|
+
* just delegates load/persist to that file's helpers; it exists so route
|
|
7
|
+
* handlers can read/write without each one re-resolving `configPath(dataDir)`.
|
|
8
|
+
*
|
|
9
|
+
* PUT semantics live in routes/settings.ts; this class is purely an I/O
|
|
10
|
+
* boundary so tests can swap it for an in-memory fake.
|
|
11
|
+
*/
|
|
12
|
+
import { type HostConfigFile } from '../installer/config.js';
|
|
13
|
+
export interface HostSettingsStoreOptions {
|
|
14
|
+
/** Resolved cadre-host data dir. */
|
|
15
|
+
dataDir: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class HostSettingsStore {
|
|
18
|
+
private readonly configPath;
|
|
19
|
+
constructor(opts: HostSettingsStoreOptions);
|
|
20
|
+
/** Read the current host.config.json (runs v1→v2 migration on first read). */
|
|
21
|
+
read(): HostConfigFile;
|
|
22
|
+
/** Patch and persist. */
|
|
23
|
+
update(patch: Partial<Omit<HostConfigFile, 'version'>>): HostConfigFile;
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings store — read-write facade around `host.config.json` for the
|
|
3
|
+
* /api/settings route.
|
|
4
|
+
*
|
|
5
|
+
* The installer (`6.4.1`) owns the schema (`HostConfigFile` v2). This module
|
|
6
|
+
* just delegates load/persist to that file's helpers; it exists so route
|
|
7
|
+
* handlers can read/write without each one re-resolving `configPath(dataDir)`.
|
|
8
|
+
*
|
|
9
|
+
* PUT semantics live in routes/settings.ts; this class is purely an I/O
|
|
10
|
+
* boundary so tests can swap it for an in-memory fake.
|
|
11
|
+
*/
|
|
12
|
+
import { configPath } from '../installer/paths.js';
|
|
13
|
+
import { readHostConfig, updateHostConfig } from '../installer/config.js';
|
|
14
|
+
export class HostSettingsStore {
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.configPath = configPath(opts.dataDir);
|
|
17
|
+
}
|
|
18
|
+
/** Read the current host.config.json (runs v1→v2 migration on first read). */
|
|
19
|
+
read() {
|
|
20
|
+
return readHostConfig(this.configPath);
|
|
21
|
+
}
|
|
22
|
+
/** Patch and persist. */
|
|
23
|
+
update(patch) {
|
|
24
|
+
return updateHostConfig(this.configPath, patch);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=settings-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-store.js","sourceRoot":"","sources":["../../src/server/settings-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAuB,MAAM,wBAAwB,CAAC;AAO/F,MAAM,OAAO,iBAAiB;IAG5B,YAAY,IAA8B;QACxC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,8EAA8E;IAC9E,IAAI;QACF,OAAO,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzC,CAAC;IAED,yBAAyB;IACzB,MAAM,CAAC,KAA+C;QACpD,OAAO,gBAAgB,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static-asset mount for the SPA bundle.
|
|
3
|
+
*
|
|
4
|
+
* The SPA bundle is produced by `6.5.2-cadre-host-local-ui-spa` into
|
|
5
|
+
* `<package>/dist/ui/`. When that directory exists we serve files from it
|
|
6
|
+
* with an `index.html` SPA fallback. When it's missing (running from source
|
|
7
|
+
* before the SPA ticket lands) we serve a placeholder page that explains
|
|
8
|
+
* how to install/build the SPA.
|
|
9
|
+
*
|
|
10
|
+
* Hand-rolled rather than pulling in `@fastify/static` — the surface area we
|
|
11
|
+
* need is tiny and the dep would only matter under tests that don't exercise
|
|
12
|
+
* static serving anyway.
|
|
13
|
+
*/
|
|
14
|
+
import type { FastifyInstance } from 'fastify';
|
|
15
|
+
/**
|
|
16
|
+
* Default location of the SPA bundle when `cadre-host` runs from its
|
|
17
|
+
* installed package.
|
|
18
|
+
* - dist/server/static.js → ../ui → packages/cadre-host/dist/ui
|
|
19
|
+
*/
|
|
20
|
+
export declare function defaultStaticRoot(): string;
|
|
21
|
+
export interface StaticMountOptions {
|
|
22
|
+
/** Absolute path to the SPA build output (defaults to `defaultStaticRoot()`). */
|
|
23
|
+
rootDir?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function registerStaticMount(app: FastifyInstance, opts?: StaticMountOptions): void;
|