@serfab/cadre-host 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -423
- package/dist/bin/host.d.ts +6 -3
- package/dist/bin/host.js +301 -505
- package/dist/bin/host.js.map +1 -1
- package/dist/bin/join-output.d.ts +56 -0
- package/dist/bin/join-output.js +92 -0
- package/dist/bin/join-output.js.map +1 -0
- package/dist/bin/nat-output.d.ts +49 -0
- package/dist/bin/nat-output.js +127 -0
- package/dist/bin/nat-output.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +44 -0
- package/dist/hosted/claim-details.js +66 -0
- package/dist/hosted/claim-details.js.map +1 -0
- package/dist/hosted/hosted-node-service.d.ts +318 -0
- package/dist/hosted/hosted-node-service.js +666 -0
- package/dist/hosted/hosted-node-service.js.map +1 -0
- package/dist/hosted/hosted-node-store.d.ts +40 -0
- package/dist/hosted/hosted-node-store.js +126 -0
- package/dist/hosted/hosted-node-store.js.map +1 -0
- package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
- package/dist/hosted/hosted-node-supervisor.js +396 -0
- package/dist/hosted/hosted-node-supervisor.js.map +1 -0
- package/dist/hosted/hosted-node-watcher.d.ts +83 -0
- package/dist/hosted/hosted-node-watcher.js +224 -0
- package/dist/hosted/hosted-node-watcher.js.map +1 -0
- package/dist/hosted/index.d.ts +16 -0
- package/dist/hosted/index.js +13 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/hosted/node-status.d.ts +35 -0
- package/dist/hosted/node-status.js +61 -0
- package/dist/hosted/node-status.js.map +1 -0
- package/dist/hosted/types.d.ts +134 -0
- package/dist/hosted/types.js +22 -0
- package/dist/hosted/types.js.map +1 -0
- package/dist/index.d.ts +12 -24
- package/dist/index.js +9 -19
- package/dist/index.js.map +1 -1
- package/dist/installer/config.d.ts +4 -34
- package/dist/installer/config.js +3 -18
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +7 -19
- package/dist/installer/index.js +14 -69
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/paths.d.ts +0 -2
- package/dist/installer/paths.js +0 -4
- package/dist/installer/paths.js.map +1 -1
- package/dist/installer/wizard.d.ts +0 -7
- package/dist/installer/wizard.js +1 -8
- package/dist/installer/wizard.js.map +1 -1
- package/dist/nat/address-resolver.d.ts +18 -17
- package/dist/nat/address-resolver.js +79 -25
- package/dist/nat/address-resolver.js.map +1 -1
- package/dist/nat/address-watch.d.ts +37 -0
- package/dist/nat/address-watch.js +84 -0
- package/dist/nat/address-watch.js.map +1 -0
- package/dist/nat/ddns/duckdns.d.ts +2 -1
- package/dist/nat/ddns/duckdns.js +5 -1
- package/dist/nat/ddns/duckdns.js.map +1 -1
- package/dist/nat/ddns/index.d.ts +6 -0
- package/dist/nat/ddns/index.js.map +1 -1
- package/dist/nat/index.d.ts +12 -9
- package/dist/nat/index.js +6 -5
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +203 -77
- package/dist/nat/nat-service.js +602 -237
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.d.ts +12 -5
- package/dist/nat/nat-store.js +114 -30
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/port-mapper.d.ts +83 -79
- package/dist/nat/port-mapper.js +148 -189
- package/dist/nat/port-mapper.js.map +1 -1
- package/dist/nat/reachability.d.ts +31 -15
- package/dist/nat/reachability.js +97 -22
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +78 -27
- package/dist/nat/types.js +8 -5
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
- package/dist/orchestrator/host-process-orchestrator.js +78 -218
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
- package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
- package/dist/orchestrator/identity-file.js.map +1 -0
- package/dist/orchestrator/index.d.ts +2 -3
- package/dist/orchestrator/index.js +1 -1
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/node-identity.d.ts +6 -6
- package/dist/orchestrator/node-identity.js +7 -7
- package/dist/orchestrator/node-identity.js.map +1 -1
- package/dist/orchestrator/port-allocator.d.ts +5 -6
- package/dist/orchestrator/port-allocator.js +9 -11
- package/dist/orchestrator/port-allocator.js.map +1 -1
- package/dist/orchestrator/state-store.d.ts +3 -5
- package/dist/orchestrator/state-store.js +2 -10
- package/dist/orchestrator/state-store.js.map +1 -1
- package/dist/orchestrator/types.d.ts +33 -22
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/server/error-handler.d.ts +5 -25
- package/dist/server/error-handler.js +11 -74
- package/dist/server/error-handler.js.map +1 -1
- package/dist/server/events/types.d.ts +7 -11
- package/dist/server/events/types.js +2 -1
- package/dist/server/events/types.js.map +1 -1
- package/dist/server/index.d.ts +11 -37
- package/dist/server/index.js +30 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/hosted-nodes.d.ts +28 -0
- package/dist/server/routes/hosted-nodes.js +68 -0
- package/dist/server/routes/hosted-nodes.js.map +1 -0
- package/dist/server/routes/nat.d.ts +4 -1
- package/dist/server/routes/nat.js +13 -2
- package/dist/server/routes/nat.js.map +1 -1
- package/dist/server/routes/nodes.d.ts +6 -20
- package/dist/server/routes/nodes.js +7 -86
- package/dist/server/routes/nodes.js.map +1 -1
- package/dist/server/routes/settings.d.ts +2 -7
- package/dist/server/routes/settings.js +3 -7
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.d.ts +2 -25
- package/dist/server/routes/status.js +1 -11
- package/dist/server/routes/status.js.map +1 -1
- package/dist/server/server.js +8 -8
- package/dist/server/settings-store.d.ts +2 -2
- package/dist/server/settings-store.js +2 -2
- package/dist/server/static.js +1 -2
- package/dist/server/static.js.map +1 -1
- package/dist/ui/assets/index-4FWuU8ye.js +19 -0
- package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
- package/dist/ui/assets/index-Di9Q4d01.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/store.d.ts +1 -1
- package/dist/update/store.js +1 -1
- package/dist/update/types.d.ts +1 -1
- package/dist/update/types.js +1 -1
- package/package.json +5 -6
- package/src/__tests__/cli-nat.smoke.test.ts +219 -212
- package/src/__tests__/cli.smoke.test.ts +1 -1
- package/src/__tests__/global-setup.ts +2 -3
- package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
- package/src/__tests__/orchestrator-handle-contract.ts +134 -134
- package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
- package/src/__tests__/orchestrator-ports.test.ts +124 -143
- package/src/__tests__/orchestrator-push.test.ts +138 -146
- package/src/__tests__/orchestrator.test.ts +1019 -1063
- package/src/bin/host.ts +368 -553
- package/src/bin/join-output.ts +120 -0
- package/src/bin/nat-output.ts +177 -0
- package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
- package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
- package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
- package/src/hosted/__tests__/test-invitation.ts +33 -0
- package/src/hosted/claim-details.ts +96 -0
- package/src/hosted/hosted-node-service.ts +795 -0
- package/src/hosted/hosted-node-store.ts +144 -0
- package/src/hosted/hosted-node-supervisor.ts +439 -0
- package/src/hosted/hosted-node-watcher.ts +250 -0
- package/src/hosted/index.ts +53 -0
- package/src/hosted/node-status.ts +71 -0
- package/src/hosted/types.ts +153 -0
- package/src/index.ts +72 -91
- package/src/installer/__tests__/config.test.ts +15 -40
- package/src/installer/__tests__/installer.smoke.test.ts +5 -86
- package/src/installer/__tests__/wizard.test.ts +56 -64
- package/src/installer/config.ts +135 -175
- package/src/installer/index.ts +15 -83
- package/src/installer/paths.ts +104 -109
- package/src/installer/wizard.ts +87 -103
- package/src/nat/__tests__/address-resolver.test.ts +107 -82
- package/src/nat/__tests__/nat-service.test.ts +433 -408
- package/src/nat/__tests__/nat-store.test.ts +127 -93
- package/src/nat/__tests__/port-mapper.test.ts +30 -191
- package/src/nat/__tests__/reachability.test.ts +96 -13
- package/src/nat/address-resolver.ts +94 -52
- package/src/nat/address-watch.ts +92 -0
- package/src/nat/ddns/duckdns.ts +5 -1
- package/src/nat/ddns/index.ts +6 -0
- package/src/nat/index.ts +87 -71
- package/src/nat/nat-service.ts +889 -501
- package/src/nat/nat-store.ts +237 -154
- package/src/nat/port-mapper.ts +241 -329
- package/src/nat/reachability.ts +134 -25
- package/src/nat/types.ts +209 -159
- package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
- package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
- package/src/orchestrator/host-process-orchestrator.ts +81 -243
- package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
- package/src/orchestrator/index.ts +9 -10
- package/src/orchestrator/node-identity.ts +41 -41
- package/src/orchestrator/port-allocator.ts +71 -73
- package/src/orchestrator/state-store.ts +76 -90
- package/src/orchestrator/types.ts +34 -23
- package/src/server/__tests__/bus.test.ts +38 -38
- package/src/server/__tests__/error-handler.test.ts +81 -118
- package/src/server/__tests__/fakes.ts +40 -44
- package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
- package/src/server/__tests__/nodes-route.test.ts +114 -283
- package/src/server/__tests__/publishers.test.ts +193 -180
- package/src/server/__tests__/server.smoke.test.ts +134 -223
- package/src/server/__tests__/settings-route.test.ts +179 -195
- package/src/server/__tests__/sse-route.test.ts +119 -119
- package/src/server/__tests__/status-route.test.ts +82 -121
- package/src/server/error-handler.ts +112 -178
- package/src/server/events/types.ts +20 -24
- package/src/server/index.ts +223 -267
- package/src/server/routes/hosted-nodes.ts +83 -0
- package/src/server/routes/nat.ts +75 -63
- package/src/server/routes/nodes.ts +96 -188
- package/src/server/routes/settings.ts +115 -123
- package/src/server/routes/status.ts +102 -134
- package/src/server/server.ts +8 -8
- package/src/server/settings-store.ts +2 -2
- package/src/server/static.ts +1 -2
- package/src/update/index.ts +2 -2
- package/src/update/store.ts +1 -1
- package/src/update/types.ts +1 -1
- package/dist/auth/duration.d.ts +0 -1
- package/dist/auth/duration.js +0 -32
- package/dist/auth/duration.js.map +0 -1
- package/dist/auth/index.d.ts +0 -11
- package/dist/auth/index.js +0 -9
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/self-label.d.ts +0 -36
- package/dist/auth/self-label.js +0 -57
- package/dist/auth/self-label.js.map +0 -1
- package/dist/auth/trust-circle-store.d.ts +0 -26
- package/dist/auth/trust-circle-store.js +0 -129
- package/dist/auth/trust-circle-store.js.map +0 -1
- package/dist/auth/trust-circle.d.ts +0 -156
- package/dist/auth/trust-circle.js +0 -307
- package/dist/auth/trust-circle.js.map +0 -1
- package/dist/auth/types.d.ts +0 -84
- package/dist/auth/types.js +0 -24
- package/dist/auth/types.js.map +0 -1
- package/dist/donation/donation-service.d.ts +0 -389
- package/dist/donation/donation-service.js +0 -828
- package/dist/donation/donation-service.js.map +0 -1
- package/dist/donation/donation-store.d.ts +0 -51
- package/dist/donation/donation-store.js +0 -152
- package/dist/donation/donation-store.js.map +0 -1
- package/dist/donation/donation-supervisor.js +0 -364
- package/dist/donation/donation-supervisor.js.map +0 -1
- package/dist/donation/grant-service.d.ts +0 -80
- package/dist/donation/grant-service.js +0 -210
- package/dist/donation/grant-service.js.map +0 -1
- package/dist/donation/grant-store.d.ts +0 -38
- package/dist/donation/grant-store.js +0 -135
- package/dist/donation/grant-store.js.map +0 -1
- package/dist/donation/index.d.ts +0 -20
- package/dist/donation/index.js +0 -17
- package/dist/donation/index.js.map +0 -1
- package/dist/donation/types.d.ts +0 -224
- package/dist/donation/types.js +0 -40
- package/dist/donation/types.js.map +0 -1
- package/dist/installer/identity.js.map +0 -1
- package/dist/owner/index.d.ts +0 -11
- package/dist/owner/index.js +0 -11
- package/dist/owner/index.js.map +0 -1
- package/dist/owner/owner-node-client.d.ts +0 -110
- package/dist/owner/owner-node-client.js +0 -184
- package/dist/owner/owner-node-client.js.map +0 -1
- package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
- package/dist/server/routes/bootstrap-node-validation.js +0 -197
- package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
- package/dist/server/routes/grants-admin.d.ts +0 -29
- package/dist/server/routes/grants-admin.js +0 -58
- package/dist/server/routes/grants-admin.js.map +0 -1
- package/dist/server/routes/grants.d.ts +0 -28
- package/dist/server/routes/grants.js +0 -165
- package/dist/server/routes/grants.js.map +0 -1
- package/dist/server/routes/provision-request-validation.d.ts +0 -45
- package/dist/server/routes/provision-request-validation.js +0 -97
- package/dist/server/routes/provision-request-validation.js.map +0 -1
- package/dist/server/routes/strands.d.ts +0 -20
- package/dist/server/routes/strands.js +0 -38
- package/dist/server/routes/strands.js.map +0 -1
- package/dist/server/routes/trust-circle.d.ts +0 -17
- package/dist/server/routes/trust-circle.js +0 -35
- package/dist/server/routes/trust-circle.js.map +0 -1
- package/dist/strands/index.d.ts +0 -7
- package/dist/strands/index.js +0 -6
- package/dist/strands/index.js.map +0 -1
- package/dist/strands/strand-service.d.ts +0 -50
- package/dist/strands/strand-service.js +0 -103
- package/dist/strands/strand-service.js.map +0 -1
- package/dist/strands/types.d.ts +0 -93
- package/dist/strands/types.js +0 -23
- package/dist/strands/types.js.map +0 -1
- package/dist/ui/assets/index-C6nELNfC.js +0 -23
- package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
- package/dist/ui/assets/index-TtIYSeZO.css +0 -1
- package/src/__tests__/cli-invite.smoke.test.ts +0 -192
- package/src/__tests__/host-owner.smoke.test.ts +0 -123
- package/src/__tests__/orchestrator-owner.test.ts +0 -234
- package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
- package/src/auth/__tests__/duration.test.ts +0 -28
- package/src/auth/__tests__/self-label.test.ts +0 -85
- package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
- package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
- package/src/auth/__tests__/trust-circle.test.ts +0 -445
- package/src/auth/duration.ts +0 -33
- package/src/auth/index.ts +0 -21
- package/src/auth/self-label.ts +0 -74
- package/src/auth/trust-circle-store.ts +0 -155
- package/src/auth/trust-circle.ts +0 -395
- package/src/auth/types.ts +0 -99
- package/src/donation/__tests__/donation-service.test.ts +0 -1193
- package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
- package/src/donation/__tests__/grant-service.test.ts +0 -213
- package/src/donation/__tests__/grant-store.test.ts +0 -113
- package/src/donation/donation-service.ts +0 -1033
- package/src/donation/donation-store.ts +0 -176
- package/src/donation/donation-supervisor.ts +0 -411
- package/src/donation/grant-service.ts +0 -252
- package/src/donation/grant-store.ts +0 -152
- package/src/donation/index.ts +0 -65
- package/src/donation/types.ts +0 -260
- package/src/owner/__tests__/owner-node-client.test.ts +0 -194
- package/src/owner/index.ts +0 -12
- package/src/owner/owner-node-client.ts +0 -259
- package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
- package/src/server/__tests__/grants-admin-route.test.ts +0 -171
- package/src/server/__tests__/grants-route.test.ts +0 -394
- package/src/server/__tests__/strands-route.test.ts +0 -234
- package/src/server/routes/bootstrap-node-validation.ts +0 -206
- package/src/server/routes/grants-admin.ts +0 -74
- package/src/server/routes/grants.ts +0 -193
- package/src/server/routes/provision-request-validation.ts +0 -105
- package/src/server/routes/strands.ts +0 -54
- package/src/server/routes/trust-circle.ts +0 -49
- package/src/strands/__tests__/strand-service.test.ts +0 -165
- package/src/strands/index.ts +0 -14
- package/src/strands/strand-service.ts +0 -135
- package/src/strands/types.ts +0 -102
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.dot.svelte-1qa2kn2{display:inline-flex;align-items:center;gap:.5rem;font-weight:500}.bulb.svelte-1qa2kn2{display:inline-block;border-radius:999px;flex:0 0 auto;background:var(--color-text-soft);box-shadow:0 0 0 3px #0000000f}.dot-ok.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-success);box-shadow:0 0 0 3px var(--color-success-bg)}.dot-warn.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-warn);box-shadow:0 0 0 3px var(--color-warn-bg)}.dot-err.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-danger);box-shadow:0 0 0 3px var(--color-danger-bg)}.dot-muted.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-text-soft)}.dot-ok.svelte-1qa2kn2{color:var(--color-success)}.dot-warn.svelte-1qa2kn2{color:var(--color-warn)}.dot-err.svelte-1qa2kn2{color:var(--color-danger)}.banner.svelte-15mtls8{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius);background:var(--color-info-bg);color:var(--color-info);border:1px solid var(--color-info)}.summary.svelte-15mtls8{grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}.kv.svelte-15mtls8{margin:var(--space-3) 0 0 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-15mtls8>div:where(.svelte-15mtls8){display:contents}.kv.svelte-15mtls8 dt:where(.svelte-15mtls8){color:var(--color-text-muted)}.kv.svelte-15mtls8 dd:where(.svelte-15mtls8){margin:0}.big.svelte-15mtls8{font-size:1.75rem;font-weight:600;margin:var(--space-2) 0}.big.svelte-15mtls8 .muted:where(.svelte-15mtls8){font-size:.95rem;font-weight:400}.none.svelte-15mtls8{margin:var(--space-2) 0;color:var(--color-warn);font-weight:500}.link.svelte-15mtls8{display:inline-block;margin-top:var(--space-2);font-weight:500}.row.svelte-io9f1k{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.kv.svelte-io9f1k{margin:0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-io9f1k>div:where(.svelte-io9f1k){display:contents}.kv.svelte-io9f1k dt:where(.svelte-io9f1k){color:var(--color-text-muted)}.kv.svelte-io9f1k dd:where(.svelte-io9f1k){margin:0;min-width:0}.addrs.svelte-io9f1k{list-style:none;margin:0;padding:0}.addrs.svelte-io9f1k li:where(.svelte-io9f1k){display:flex;align-items:center;gap:.25rem}.addrs.svelte-io9f1k code:where(.svelte-io9f1k){word-break:break-all}.reach.svelte-io9f1k p:where(.svelte-io9f1k){margin:0}.instruction.svelte-io9f1k{font-weight:500}summary.svelte-io9f1k{cursor:pointer;font-size:.9rem;font-weight:500}.forward.svelte-io9f1k{display:grid;gap:var(--space-2);margin-top:var(--space-2)}.fields.svelte-io9f1k{display:grid;gap:var(--space-2);grid-template-columns:repeat(auto-fit,minmax(12rem,1fr))}.actions.svelte-io9f1k{display:flex;justify-content:flex-end;gap:.5rem}.small.svelte-io9f1k{font-size:.85rem}.error.svelte-io9f1k{color:var(--color-danger)}.grid.two.svelte-hh3bto{grid-template-columns:repeat(auto-fit,minmax(20rem,1fr))}.kv.svelte-hh3bto{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-hh3bto>div:where(.svelte-hh3bto){display:contents}.kv.svelte-hh3bto dt:where(.svelte-hh3bto){color:var(--color-text-muted)}.kv.svelte-hh3bto dd:where(.svelte-hh3bto){margin:0}.row.inline.svelte-hh3bto{gap:.5rem}.row.inline.svelte-hh3bto input[type=checkbox]:where(.svelte-hh3bto){width:auto}.nodes.svelte-hh3bto{list-style:none;margin:0;padding:0;display:grid;gap:var(--space-3)}.nodes.svelte-hh3bto li:where(.svelte-hh3bto){border-top:1px solid var(--color-border);padding-top:var(--space-3)}.nodes.svelte-hh3bto li:where(.svelte-hh3bto):first-child{border-top:0;padding-top:0}.warning.svelte-hh3bto{background:var(--color-warn-bg);border-color:var(--color-warn)}.actions.svelte-hh3bto{display:flex;justify-content:flex-end;gap:.5rem}.small.svelte-hh3bto{font-size:.85rem}.scroll.svelte-7tcksg{overflow-x:auto}table.svelte-7tcksg{width:100%;border-collapse:collapse;font-size:.92rem}th.svelte-7tcksg,td.svelte-7tcksg{padding:.5rem .75rem;text-align:left;border-bottom:1px solid var(--color-border);white-space:nowrap}th.svelte-7tcksg{font-weight:600;font-size:.78rem;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-muted);border-bottom-color:var(--color-border-strong)}tr.svelte-7tcksg:last-child td:where(.svelte-7tcksg){border-bottom:none}.badges.svelte-7tcksg{display:inline-flex;gap:.25rem}.error.svelte-7tcksg{color:var(--color-danger)}.qr.svelte-1uqetyj{background:#fff;border-radius:var(--radius);padding:.5rem;display:inline-block;line-height:0}.qr.svelte-1uqetyj svg{width:100%;height:100%;display:block}.placeholder.svelte-1uqetyj{background:var(--color-surface-alt)}.qr-wrap.svelte-sddtr9{display:flex;justify-content:center;margin:var(--space-3) 0}.token.svelte-sddtr9{display:flex;flex-direction:column;gap:.5rem}.token.svelte-sddtr9 textarea:where(.svelte-sddtr9){font-family:var(--font-mono);font-size:.78rem;word-break:break-all;resize:vertical}.token.svelte-sddtr9 button:where(.svelte-sddtr9){align-self:flex-end;display:inline-flex;align-items:center;gap:.375rem}p.svelte-1jht8du{margin:0}.small.svelte-1jht8du{font-size:.85rem}.error.svelte-1jht8du{color:var(--color-danger)}.warn.svelte-1jht8du{color:var(--color-warn);font-weight:500}.backdrop.svelte-80vbit{position:fixed;inset:0;background:#0f111673;z-index:1000}.dialog.svelte-80vbit{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);padding:var(--space-5);min-width:min(24rem,calc(100vw - 2rem));max-width:min(32rem,calc(100vw - 2rem));overflow-wrap:anywhere;z-index:1010}.note.svelte-80vbit{background:var(--color-warn-bg);color:var(--color-warn);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:.9rem}.actions.svelte-80vbit{display:flex;justify-content:flex-end;gap:.5rem;margin-top:var(--space-4)}.log-card.svelte-15ufwrh{display:flex;flex-direction:column;gap:var(--space-3)}.header.svelte-15ufwrh{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3)}.logs.svelte-15ufwrh{max-height:24rem;font-size:.78rem;line-height:1.45;white-space:pre}.small.svelte-15ufwrh{font-size:.85rem}button.svelte-15ufwrh{display:inline-flex;align-items:center;gap:.375rem}.back.svelte-1eq4g96{font-size:.92rem}.page-header.svelte-1eq4g96{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3)}.page-header.svelte-1eq4g96 h2:where(.svelte-1eq4g96) code:where(.svelte-1eq4g96){word-break:break-all}.card-head.svelte-1eq4g96{display:flex;align-items:center;gap:.5rem}.card-head.svelte-1eq4g96 h3:where(.svelte-1eq4g96){margin:0}.state.svelte-1eq4g96{margin:0;font-weight:500}.small.svelte-1eq4g96{font-size:.85rem;margin:0}.error.svelte-1eq4g96{color:var(--color-danger)}.kv.svelte-1eq4g96{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.92rem}.kv.svelte-1eq4g96>div:where(.svelte-1eq4g96){display:contents}.kv.svelte-1eq4g96 dt:where(.svelte-1eq4g96){color:var(--color-text-muted)}.kv.svelte-1eq4g96 dd:where(.svelte-1eq4g96){margin:0;min-width:0;overflow-wrap:anywhere}.actions.svelte-1eq4g96{display:flex;justify-content:flex-end;gap:.5rem;flex-wrap:wrap}.page-header.svelte-cshs6v{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3)}.page-header.svelte-cshs6v>div:where(.svelte-cshs6v){flex:1 1 20rem}.invitation.svelte-cshs6v p:where(.svelte-cshs6v){margin:0}.invitation.svelte-cshs6v textarea:where(.svelte-cshs6v){width:100%;box-sizing:border-box;font-family:var(--font-mono);font-size:.85rem;word-break:break-all}.node-head.svelte-cshs6v{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.node-head.svelte-cshs6v code:where(.svelte-cshs6v){word-break:break-all}.state.svelte-cshs6v{margin:0;font-weight:500}.small.svelte-cshs6v{font-size:.85rem;margin:0}.actions.svelte-cshs6v{display:flex;justify-content:flex-end;gap:.5rem;flex-wrap:wrap}.error.svelte-cshs6v{color:var(--color-danger)}.row.inline.svelte-1cl3wo6{gap:.5rem}.row.inline.svelte-1cl3wo6 input[type=checkbox]:where(.svelte-1cl3wo6){width:auto}.banner.svelte-1cl3wo6{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3);border-radius:var(--radius);background:var(--color-info-bg);color:var(--color-info);border:1px solid var(--color-info);flex-wrap:wrap}.kv.svelte-1cl3wo6{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.92rem}.kv.svelte-1cl3wo6>div:where(.svelte-1cl3wo6){display:contents}.kv.svelte-1cl3wo6 dt:where(.svelte-1cl3wo6){color:var(--color-text-muted)}.kv.svelte-1cl3wo6 dd:where(.svelte-1cl3wo6){margin:0}.copy-row.svelte-1cl3wo6{display:flex;align-items:center;gap:.375rem}.actions.svelte-1cl3wo6{display:flex;justify-content:flex-end}.small.svelte-1cl3wo6{font-size:.85rem}.error.svelte-1cl3wo6{color:var(--color-danger)}.toast-stack.svelte-g7yjw8{position:fixed;right:1rem;bottom:1rem;display:flex;flex-direction:column;gap:.5rem;max-width:min(30rem,calc(100vw - 2rem));z-index:1100}.toast.svelte-g7yjw8{background:var(--color-surface);color:var(--color-text);border:1px solid var(--color-border-strong);border-radius:var(--radius);padding:.625rem .75rem;display:flex;align-items:center;gap:.5rem;box-shadow:var(--shadow);font-size:.92rem}.toast-error.svelte-g7yjw8{border-color:var(--color-danger);background:var(--color-danger-bg);color:var(--color-danger)}.toast-success.svelte-g7yjw8{border-color:var(--color-success);background:var(--color-success-bg);color:var(--color-success)}.text.svelte-g7yjw8{flex:1 1 auto}button.svelte-g7yjw8{font-size:1.1rem;line-height:1;padding:.125rem .5rem;color:currentColor}.topbar.svelte-1r8vfx3{position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);padding:var(--space-3) var(--space-5);background:var(--color-surface);border-bottom:1px solid var(--color-border);backdrop-filter:saturate(180%) blur(8px)}.brand.svelte-1r8vfx3{display:flex;align-items:center;gap:.6rem;font-weight:600}.logo.svelte-1r8vfx3{color:var(--color-accent);font-size:1.1rem}nav.svelte-1r8vfx3{display:flex;gap:.125rem}nav.svelte-1r8vfx3 a:where(.svelte-1r8vfx3){font-size:.92rem;color:var(--color-text-muted);padding:.375rem .75rem;border-radius:var(--radius);text-decoration:none}nav.svelte-1r8vfx3 a:where(.svelte-1r8vfx3):hover{background:var(--color-surface-alt);color:var(--color-text)}nav.svelte-1r8vfx3 a.active:where(.svelte-1r8vfx3){color:var(--color-accent-on);background:var(--color-accent)}main.svelte-1r8vfx3{flex:1 1 auto;width:min(64rem,100%);margin:0 auto;padding:var(--space-5)}@media(max-width:640px){.topbar.svelte-1r8vfx3{flex-direction:column;align-items:flex-start;gap:var(--space-2);padding:var(--space-2) var(--space-3)}nav.svelte-1r8vfx3{width:100%;overflow-x:auto}main.svelte-1r8vfx3{padding:var(--space-3)}}:root{color-scheme:light dark;--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Menlo, monospace;--radius-sm: .25rem;--radius: .5rem;--radius-lg: .75rem;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.5rem;--space-6: 2rem;--space-7: 3rem;--color-bg: #f7f8fa;--color-surface: #ffffff;--color-surface-alt: #f0f2f6;--color-border: #d9dde3;--color-border-strong: #b8bec7;--color-text: #1d1f24;--color-text-muted: #5b626d;--color-text-soft: #8a8f99;--color-accent: #1d4ed8;--color-accent-strong: #1e3a8a;--color-accent-on: #ffffff;--color-accent-soft: #e0e7ff;--color-success: #15803d;--color-success-bg: #dcfce7;--color-warn: #b45309;--color-warn-bg: #fef3c7;--color-danger: #b91c1c;--color-danger-bg: #fee2e2;--color-info: #1d4ed8;--color-info-bg: #dbeafe;--shadow-sm: 0 1px 2px rgba(15, 23, 42, .05);--shadow: 0 4px 12px rgba(15, 23, 42, .08);--shadow-lg: 0 12px 32px rgba(15, 23, 42, .16)}@media(prefers-color-scheme:dark){:root{--color-bg: #0f1116;--color-surface: #181b22;--color-surface-alt: #20242d;--color-border: #2c313c;--color-border-strong: #3b424f;--color-text: #e8eaf0;--color-text-muted: #a4abb8;--color-text-soft: #777e8b;--color-accent: #93c5fd;--color-accent-strong: #bfdbfe;--color-accent-on: #0f1116;--color-accent-soft: #1e293b;--color-success: #4ade80;--color-success-bg: #14532d;--color-warn: #fbbf24;--color-warn-bg: #78350f;--color-danger: #f87171;--color-danger-bg: #7f1d1d;--color-info: #93c5fd;--color-info-bg: #1e3a8a;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);--shadow: 0 4px 12px rgba(0, 0, 0, .45);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .6)}}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0;background:var(--color-bg);color:var(--color-text);font-family:var(--font-sans);font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased;min-height:100vh}#app{min-height:100vh;display:flex;flex-direction:column}h1,h2,h3,h4,h5,h6{margin:0 0 var(--space-2) 0;font-weight:600;line-height:1.25;letter-spacing:-.01em}h1{font-size:1.5rem}h2{font-size:1.25rem}h3{font-size:1.05rem}p{margin:0 0 var(--space-3) 0}a{color:var(--color-accent);text-decoration:none}a:hover{text-decoration:underline}button{font:inherit;cursor:pointer;border:1px solid var(--color-border-strong);background:var(--color-surface);color:var(--color-text);padding:var(--space-2) var(--space-3);border-radius:var(--radius);transition:background-color .12s ease,border-color .12s ease}button:hover:not(:disabled){background:var(--color-surface-alt);border-color:var(--color-text-muted)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}button:disabled{opacity:.55;cursor:not-allowed}button.primary{background:var(--color-accent);border-color:var(--color-accent);color:var(--color-accent-on)}button.primary:hover:not(:disabled){background:var(--color-accent-strong);border-color:var(--color-accent-strong)}button.danger{background:var(--color-danger);border-color:var(--color-danger);color:#fff}button.ghost{background:transparent;border-color:transparent;color:var(--color-text-muted);padding:var(--space-1) var(--space-2)}button.ghost:hover:not(:disabled){color:var(--color-text);background:var(--color-surface-alt)}input[type=text],input[type=url],input[type=number],input[type=password],select,textarea{font:inherit;color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border-strong);border-radius:var(--radius);padding:var(--space-2) var(--space-3);width:100%}label{display:block;font-size:.85rem;color:var(--color-text-muted);font-weight:500;margin-bottom:var(--space-1)}code,pre{font-family:var(--font-mono);font-size:.85rem}pre{background:var(--color-surface-alt);padding:var(--space-3);border-radius:var(--radius);overflow:auto;margin:0}hr{border:none;border-top:1px solid var(--color-border);margin:var(--space-5) 0}.card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:var(--space-5);box-shadow:var(--shadow-sm)}.muted{color:var(--color-text-muted)}.row{display:flex;gap:var(--space-3);align-items:center;flex-wrap:wrap}.stack{display:flex;flex-direction:column;gap:var(--space-3)}.grid{display:grid;gap:var(--space-4)}.badge{display:inline-flex;align-items:center;gap:var(--space-1);font-size:.78rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;padding:.125rem .5rem;border-radius:999px;background:var(--color-surface-alt);color:var(--color-text-muted)}.badge.ok{background:var(--color-success-bg);color:var(--color-success)}.badge.warn{background:var(--color-warn-bg);color:var(--color-warn)}.badge.err{background:var(--color-danger-bg);color:var(--color-danger)}.badge.info{background:var(--color-info-bg);color:var(--color-info)}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
package/dist/ui/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="color-scheme" content="light dark" />
|
|
7
7
|
<title>cadre-host</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-4FWuU8ye.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="./assets/index-Di9Q4d01.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="app"></div>
|
package/dist/update/index.d.ts
CHANGED
|
@@ -98,8 +98,8 @@ export declare class UpdateService {
|
|
|
98
98
|
stop(): void;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
|
-
* Typed handler facade —
|
|
102
|
-
*
|
|
101
|
+
* Typed handler facade — same shape as `createNatHandlers`. The local-UI
|
|
102
|
+
* server consumes these.
|
|
103
103
|
*/
|
|
104
104
|
export declare function createUpdateHandlers(service: UpdateService): UpdateHandlers;
|
|
105
105
|
export type { UpdateState, UpdateApplyResult, UpdateSettings, UpdateManifest, SignedManifest } from './types.js';
|
package/dist/update/index.js
CHANGED
|
@@ -231,8 +231,8 @@ export class UpdateService {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
/**
|
|
234
|
-
* Typed handler facade —
|
|
235
|
-
*
|
|
234
|
+
* Typed handler facade — same shape as `createNatHandlers`. The local-UI
|
|
235
|
+
* server consumes these.
|
|
236
236
|
*/
|
|
237
237
|
export function createUpdateHandlers(service) {
|
|
238
238
|
return {
|
package/dist/update/store.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Atomic JSON store for `<dataDir>/update-state.json`.
|
|
3
3
|
*
|
|
4
|
-
* Mirrors `
|
|
4
|
+
* Mirrors `NatStore`: load on demand, write to
|
|
5
5
|
* `<path>.tmp` then rename. Single-writer assumption — only one cadre-host
|
|
6
6
|
* process owns a given dataDir.
|
|
7
7
|
*/
|
package/dist/update/store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Atomic JSON store for `<dataDir>/update-state.json`.
|
|
3
3
|
*
|
|
4
|
-
* Mirrors `
|
|
4
|
+
* Mirrors `NatStore`: load on demand, write to
|
|
5
5
|
* `<path>.tmp` then rename. Single-writer assumption — only one cadre-host
|
|
6
6
|
* process owns a given dataDir.
|
|
7
7
|
*/
|
package/dist/update/types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - apply.ts — invoke `npm install -g` + rollback on failure.
|
|
9
9
|
* - index.ts — composes the above (UpdateService).
|
|
10
10
|
*
|
|
11
|
-
* Mirrors the typed-handler pattern used by
|
|
11
|
+
* Mirrors the typed-handler pattern used by NAT (handlers
|
|
12
12
|
* throw `UpdateError` whose `.code` the local-UI maps to HTTP status).
|
|
13
13
|
*/
|
|
14
14
|
/** Inner manifest object — signed by the release key. */
|
package/dist/update/types.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - apply.ts — invoke `npm install -g` + rollback on failure.
|
|
9
9
|
* - index.ts — composes the above (UpdateService).
|
|
10
10
|
*
|
|
11
|
-
* Mirrors the typed-handler pattern used by
|
|
11
|
+
* Mirrors the typed-handler pattern used by NAT (handlers
|
|
12
12
|
* throw `UpdateError` whose `.code` the local-UI maps to HTTP status).
|
|
13
13
|
*/
|
|
14
14
|
/** Thrown by UpdateService.apply() and related entry points. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serfab/cadre-host",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-hosted cadre node manager for basement-PC deployments",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
"cadre",
|
|
30
30
|
"host",
|
|
31
31
|
"self-hosted",
|
|
32
|
-
"p2p"
|
|
33
|
-
"trust-circle"
|
|
32
|
+
"p2p"
|
|
34
33
|
],
|
|
35
34
|
"scripts": {
|
|
36
35
|
"clean": "rimraf dist",
|
|
@@ -65,9 +64,9 @@
|
|
|
65
64
|
"@libp2p/interface": "^3.3.0",
|
|
66
65
|
"@libp2p/peer-id": "^6.0.15",
|
|
67
66
|
"@multiformats/multiaddr": "^13.0.3",
|
|
68
|
-
"@serfab/cadre-cli": "^1.
|
|
69
|
-
"@serfab/cadre-core": "^1.
|
|
70
|
-
"@serfab/cadre-provider": "^1.
|
|
67
|
+
"@serfab/cadre-cli": "^1.14.0",
|
|
68
|
+
"@serfab/cadre-core": "^1.14.0",
|
|
69
|
+
"@serfab/cadre-provider": "^1.14.0",
|
|
71
70
|
"commander": "^14.0.2",
|
|
72
71
|
"debug": "^4.4.3",
|
|
73
72
|
"fastify": "^5.7.1",
|
|
@@ -1,212 +1,219 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI smoke test for the `nat` subcommand. Spawns a stub HTTP server playing
|
|
3
|
-
* the role of cadre-host's management API and asserts the CLI:
|
|
4
|
-
* - GETs /nat/status
|
|
5
|
-
* - POSTs /nat/test
|
|
6
|
-
* - PUTs /nat/ddns with the expected body
|
|
7
|
-
* - PUTs /nat/settings
|
|
8
|
-
* - Exits non-zero when the host is not reachable
|
|
9
|
-
*
|
|
10
|
-
* Relies on `yarn build` having produced dist/bin/host.js.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { spawn } from 'node:child_process';
|
|
14
|
-
import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import { dirname, resolve } from 'node:path';
|
|
18
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
19
|
-
|
|
20
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
const binPath = resolve(here, '../../dist/bin/host.js');
|
|
22
|
-
|
|
23
|
-
// These smoke tests cold-start the built CLI as a child process (Node boot +,
|
|
24
|
-
// for some, an HTTP server). That comfortably exceeds vitest's 5s default under
|
|
25
|
-
// heavy parallel load, so give the subprocess generous headroom.
|
|
26
|
-
vi.setConfig({ testTimeout: 30000 });
|
|
27
|
-
|
|
28
|
-
interface StubServer {
|
|
29
|
-
server: Server;
|
|
30
|
-
port: number;
|
|
31
|
-
requests: Array<{ method: string; url: string; body: string }>;
|
|
32
|
-
setResponse(handler: (req: IncomingMessage, res: ServerResponse) => void): void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const DEFAULT_STATUS = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
let
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
expect(
|
|
123
|
-
|
|
124
|
-
expect(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const result = await runCli(['nat', 'status', '--port', String(port)]);
|
|
131
|
-
expect(result.code).toBe(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
expect(
|
|
141
|
-
expect(
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe('cadre-host nat
|
|
146
|
-
it('
|
|
147
|
-
const result = await runCli([
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
expect(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
'
|
|
175
|
-
'--
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
expect(result.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
1
|
+
/**
|
|
2
|
+
* CLI smoke test for the `nat` subcommand. Spawns a stub HTTP server playing
|
|
3
|
+
* the role of cadre-host's management API and asserts the CLI:
|
|
4
|
+
* - GETs /nat/status
|
|
5
|
+
* - POSTs /nat/test
|
|
6
|
+
* - PUTs /nat/ddns with the expected body
|
|
7
|
+
* - PUTs /nat/settings
|
|
8
|
+
* - Exits non-zero when the host is not reachable
|
|
9
|
+
*
|
|
10
|
+
* Relies on `yarn build` having produced dist/bin/host.js.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { dirname, resolve } from 'node:path';
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
19
|
+
|
|
20
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const binPath = resolve(here, '../../dist/bin/host.js');
|
|
22
|
+
|
|
23
|
+
// These smoke tests cold-start the built CLI as a child process (Node boot +,
|
|
24
|
+
// for some, an HTTP server). That comfortably exceeds vitest's 5s default under
|
|
25
|
+
// heavy parallel load, so give the subprocess generous headroom.
|
|
26
|
+
vi.setConfig({ testTimeout: 30000 });
|
|
27
|
+
|
|
28
|
+
interface StubServer {
|
|
29
|
+
server: Server;
|
|
30
|
+
port: number;
|
|
31
|
+
requests: Array<{ method: string; url: string; body: string }>;
|
|
32
|
+
setResponse(handler: (req: IncomingMessage, res: ServerResponse) => void): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DEFAULT_STATUS = {
|
|
36
|
+
upnpEnabled: true,
|
|
37
|
+
gateway: { found: true, lanAddress: '192.168.1.20', routerExternalIp: '203.0.113.5', lastError: null },
|
|
38
|
+
externalIp: '203.0.113.5',
|
|
39
|
+
externalIpDetectedAt: new Date().toISOString(),
|
|
40
|
+
cgnatDetected: false,
|
|
41
|
+
directReachability: 'reachable',
|
|
42
|
+
lastTestedAt: null,
|
|
43
|
+
nodes: [
|
|
44
|
+
{
|
|
45
|
+
nodeId: 'owner',
|
|
46
|
+
running: true,
|
|
47
|
+
verdict: 'mapped',
|
|
48
|
+
reason: null,
|
|
49
|
+
tcp: { internalPort: 4001, externalPort: 4001, source: 'upnp', leaseExpiresAt: null, error: null },
|
|
50
|
+
ws: { internalPort: 10001, externalPort: 10001, source: 'upnp', leaseExpiresAt: null, error: null },
|
|
51
|
+
publicAddrs: ['/ip4/203.0.113.5/tcp/4001', '/ip4/203.0.113.5/tcp/10001/ws'],
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
ddns: {
|
|
55
|
+
providerId: 'duckdns',
|
|
56
|
+
hostname: 'foo.duckdns.org',
|
|
57
|
+
externallyManaged: false,
|
|
58
|
+
lastUpdateAt: null,
|
|
59
|
+
lastUpdateOk: null,
|
|
60
|
+
lastError: null,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
async function startStub(): Promise<StubServer> {
|
|
65
|
+
const requests: StubServer['requests'] = [];
|
|
66
|
+
let handler: (req: IncomingMessage, res: ServerResponse) => void = (_req, res) => {
|
|
67
|
+
res.statusCode = 200;
|
|
68
|
+
res.setHeader('content-type', 'application/json');
|
|
69
|
+
res.end(JSON.stringify(DEFAULT_STATUS));
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const server = createServer((req, res) => {
|
|
73
|
+
const chunks: Buffer[] = [];
|
|
74
|
+
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
75
|
+
req.on('end', () => {
|
|
76
|
+
requests.push({ method: req.method ?? '', url: req.url ?? '', body: Buffer.concat(chunks).toString('utf8') });
|
|
77
|
+
handler(req, res);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await new Promise<void>((resolveStart) => server.listen(0, '127.0.0.1', () => resolveStart()));
|
|
82
|
+
const addr = server.address();
|
|
83
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
84
|
+
return {
|
|
85
|
+
server,
|
|
86
|
+
port,
|
|
87
|
+
requests,
|
|
88
|
+
setResponse: (h) => { handler = h; },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function runCli(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
|
|
93
|
+
return new Promise((resolveRun) => {
|
|
94
|
+
const child = spawn(process.execPath, [binPath, ...args], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
95
|
+
let stdout = '';
|
|
96
|
+
let stderr = '';
|
|
97
|
+
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8'); });
|
|
98
|
+
child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8'); });
|
|
99
|
+
child.on('close', (code) => resolveRun({ code: code ?? -1, stdout, stderr }));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let stub: StubServer;
|
|
104
|
+
|
|
105
|
+
beforeEach(async () => {
|
|
106
|
+
if (!existsSync(binPath)) {
|
|
107
|
+
throw new Error(`cadre-host bin not built at ${binPath}; run \`yarn build\` first`);
|
|
108
|
+
}
|
|
109
|
+
stub = await startStub();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
afterEach(async () => {
|
|
113
|
+
await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('cadre-host nat status', () => {
|
|
117
|
+
it('GETs /nat/status and renders the snapshot', async () => {
|
|
118
|
+
const result = await runCli(['nat', 'status', '--port', String(stub.port)]);
|
|
119
|
+
expect(result.code).toBe(0);
|
|
120
|
+
expect(stub.requests).toHaveLength(1);
|
|
121
|
+
expect(stub.requests[0]!.method).toBe('GET');
|
|
122
|
+
expect(stub.requests[0]!.url).toBe('/nat/status');
|
|
123
|
+
expect(result.stdout).toContain('reachable');
|
|
124
|
+
expect(result.stdout).toContain('203.0.113.5');
|
|
125
|
+
expect(result.stdout).toContain('owner: mapped');
|
|
126
|
+
expect(result.stdout).toContain('foo.duckdns.org');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('--json prints raw JSON', async () => {
|
|
130
|
+
const result = await runCli(['nat', 'status', '--port', String(stub.port), '--json']);
|
|
131
|
+
expect(result.code).toBe(0);
|
|
132
|
+
const parsed = JSON.parse(result.stdout);
|
|
133
|
+
expect(parsed.directReachability).toBe('reachable');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('exits 2 when cadre-host is unreachable', async () => {
|
|
137
|
+
const port = stub.port;
|
|
138
|
+
await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
|
|
139
|
+
const result = await runCli(['nat', 'status', '--port', String(port)]);
|
|
140
|
+
expect(result.code).toBe(2);
|
|
141
|
+
expect(result.stderr).toMatch(/cadre-host start|Failed to reach/);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('cadre-host nat test', () => {
|
|
146
|
+
it('POSTs /nat/test', async () => {
|
|
147
|
+
const result = await runCli(['nat', 'test', '--port', String(stub.port)]);
|
|
148
|
+
expect(result.code).toBe(0);
|
|
149
|
+
expect(stub.requests[0]!.method).toBe('POST');
|
|
150
|
+
expect(stub.requests[0]!.url).toBe('/nat/test');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('cadre-host nat ddns set', () => {
|
|
155
|
+
it('PUTs /nat/ddns with provider + hostname + token', async () => {
|
|
156
|
+
const result = await runCli([
|
|
157
|
+
'nat', 'ddns', 'set', 'duckdns',
|
|
158
|
+
'--hostname', 'foo.duckdns.org',
|
|
159
|
+
'--token', 'TOK',
|
|
160
|
+
'--port', String(stub.port),
|
|
161
|
+
]);
|
|
162
|
+
expect(result.code).toBe(0);
|
|
163
|
+
expect(stub.requests[0]!.method).toBe('PUT');
|
|
164
|
+
expect(stub.requests[0]!.url).toBe('/nat/ddns');
|
|
165
|
+
const body = JSON.parse(stub.requests[0]!.body);
|
|
166
|
+
expect(body.providerId).toBe('duckdns');
|
|
167
|
+
expect(body.hostname).toBe('foo.duckdns.org');
|
|
168
|
+
expect(body.config).toEqual({ token: 'TOK' });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('exits 1 when --hostname is missing', async () => {
|
|
172
|
+
const result = await runCli([
|
|
173
|
+
'nat', 'ddns', 'set', 'duckdns',
|
|
174
|
+
'--token', 'TOK',
|
|
175
|
+
'--port', String(stub.port),
|
|
176
|
+
]);
|
|
177
|
+
expect(result.code).toBe(1);
|
|
178
|
+
expect(result.stderr).toMatch(/--hostname/);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('exits 1 when --token is missing on a non-TTY stdin', async () => {
|
|
182
|
+
const result = await runCli([
|
|
183
|
+
'nat', 'ddns', 'set', 'duckdns',
|
|
184
|
+
'--hostname', 'foo.duckdns.org',
|
|
185
|
+
'--port', String(stub.port),
|
|
186
|
+
]);
|
|
187
|
+
expect(result.code).toBe(1);
|
|
188
|
+
expect(result.stderr).toMatch(/--token/);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('cadre-host nat ddns external', () => {
|
|
193
|
+
it('PUTs /nat/settings with externallyManaged=true', async () => {
|
|
194
|
+
const result = await runCli([
|
|
195
|
+
'nat', 'ddns', 'external',
|
|
196
|
+
'--hostname', 'mydomain.example.com',
|
|
197
|
+
'--port', String(stub.port),
|
|
198
|
+
]);
|
|
199
|
+
expect(result.code).toBe(0);
|
|
200
|
+
expect(stub.requests[0]!.method).toBe('PUT');
|
|
201
|
+
expect(stub.requests[0]!.url).toBe('/nat/settings');
|
|
202
|
+
const body = JSON.parse(stub.requests[0]!.body);
|
|
203
|
+
expect(body.ddns.hostname).toBe('mydomain.example.com');
|
|
204
|
+
expect(body.ddns.externallyManaged).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('cadre-host nat settings', () => {
|
|
209
|
+
it('PUTs /nat/settings with --no-upnp', async () => {
|
|
210
|
+
const result = await runCli([
|
|
211
|
+
'nat', 'settings',
|
|
212
|
+
'--no-upnp',
|
|
213
|
+
'--port', String(stub.port),
|
|
214
|
+
]);
|
|
215
|
+
expect(result.code).toBe(0);
|
|
216
|
+
expect(stub.requests[0]!.url).toBe('/nat/settings');
|
|
217
|
+
expect(JSON.parse(stub.requests[0]!.body)).toEqual({ upnpEnabled: false });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -28,7 +28,7 @@ describe('cadre-host CLI smoke test', () => {
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
expect(stdout.length).toBeGreaterThan(0);
|
|
31
|
-
for (const cmd of ['install', 'start', 'status', '
|
|
31
|
+
for (const cmd of ['install', 'start', 'status', 'uninstall']) {
|
|
32
32
|
expect(stdout).toContain(cmd);
|
|
33
33
|
}
|
|
34
34
|
});
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Vitest global setup — runs once before the whole cadre-host suite.
|
|
3
3
|
*
|
|
4
4
|
* `src/update/manifest.ts` and `src/push/index.ts` import real symbols from
|
|
5
|
-
* `@serfab/cadre-core` (`canonicalJson`, `validatePushCredentials`)
|
|
6
|
-
*
|
|
7
|
-
* it too. `@serfab/cadre-core`'s compiled entry point in turn imports
|
|
5
|
+
* `@serfab/cadre-core` (`canonicalJson`, `validatePushCredentials`).
|
|
6
|
+
* `@serfab/cadre-core`'s compiled entry point in turn imports
|
|
8
7
|
* `@serfab/quereus-plugin-sereus`, which imports the `@optimystic/*` and
|
|
9
8
|
* `@quereus/quereus` packages linked in from their sibling checkouts.
|
|
10
9
|
* `host-process-orchestrator.ts` also resolves `@serfab/cadre-cli`'s bin path at
|