@camstack/system 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon/addon-api-factory.d.ts +35 -0
- package/dist/addon-routes/addon-route-registry.d.ts +37 -0
- package/dist/addon-runner.js +599 -0
- package/dist/addon-runner.mjs +597 -0
- package/dist/auth/api-key-manager.d.ts +26 -0
- package/dist/auth/auth-manager.d.ts +109 -0
- package/dist/auth/parse-record.d.ts +18 -0
- package/dist/auth/scope-matcher.d.ts +7 -0
- package/dist/auth/scoped-token-manager.d.ts +40 -0
- package/dist/auth/totp-manager.d.ts +51 -0
- package/dist/auth/user-manager.d.ts +34 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts +53 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +259 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +251 -0
- package/dist/builtins/addon-pages-aggregator/dedupe-pages.d.ts +6 -0
- package/dist/builtins/addon-pages-aggregator/index.d.ts +1 -0
- package/dist/builtins/addon-pages-aggregator/index.js +8 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs +2 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +47 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +228 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +220 -0
- package/dist/builtins/addon-widgets-aggregator/index.d.ts +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.js +8 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs +2 -0
- package/dist/builtins/alerts/alerts.addon.d.ts +81 -0
- package/dist/builtins/alerts/alerts.addon.js +601 -0
- package/dist/builtins/alerts/alerts.addon.mjs +595 -0
- package/dist/builtins/alerts/index.d.ts +1 -0
- package/dist/builtins/alerts/index.js +4 -0
- package/dist/builtins/alerts/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +147 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2229 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2220 -0
- package/dist/builtins/backup-orchestrator/cron-helpers.d.ts +23 -0
- package/dist/builtins/backup-orchestrator/destination-policy.d.ts +72 -0
- package/dist/builtins/backup-orchestrator/download-helpers.d.ts +12 -0
- package/dist/builtins/backup-orchestrator/index.d.ts +2 -0
- package/dist/builtins/backup-orchestrator/index.js +8 -0
- package/dist/builtins/backup-orchestrator/index.mjs +2 -0
- package/dist/builtins/backup-orchestrator/manifest-store.d.ts +77 -0
- package/dist/builtins/console-logging/console-destination.d.ts +13 -0
- package/dist/builtins/console-logging/console-logging.addon.d.ts +25 -0
- package/dist/builtins/console-logging/index.d.ts +3 -0
- package/dist/builtins/console-logging/index.js +104 -0
- package/dist/builtins/console-logging/index.mjs +95 -0
- package/dist/builtins/device-manager/device-config-contribution.d.ts +32 -0
- package/dist/builtins/device-manager/device-event-propagator.d.ts +26 -0
- package/dist/builtins/device-manager/device-link-overlay.d.ts +23 -0
- package/dist/builtins/device-manager/device-link-resolver.d.ts +15 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +452 -0
- package/dist/builtins/device-manager/device-manager.addon.js +3299 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs +3292 -0
- package/dist/builtins/device-manager/index.d.ts +2 -0
- package/dist/builtins/device-manager/index.js +8 -0
- package/dist/builtins/device-manager/index.mjs +2 -0
- package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +44 -0
- package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts +15 -0
- package/dist/builtins/hub-forwarder/index.d.ts +3 -0
- package/dist/builtins/hub-forwarder/index.js +154 -0
- package/dist/builtins/hub-forwarder/index.mjs +145 -0
- package/dist/builtins/local-auth/auth-schema.d.ts +26 -0
- package/dist/builtins/local-auth/index.d.ts +1 -0
- package/dist/builtins/local-auth/index.js +4 -0
- package/dist/builtins/local-auth/index.mjs +2 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts +18 -0
- package/dist/builtins/local-auth/local-auth.addon.js +8094 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs +8063 -0
- package/dist/builtins/local-auth/oauth-grants.d.ts +45 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +50 -0
- package/dist/builtins/local-network/index.d.ts +2 -0
- package/dist/builtins/local-network/index.js +10 -0
- package/dist/builtins/local-network/index.mjs +2 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +150 -0
- package/dist/builtins/local-network/local-network.addon.js +489 -0
- package/dist/builtins/local-network/local-network.addon.mjs +477 -0
- package/dist/builtins/native-metrics/index.d.ts +2 -0
- package/dist/builtins/native-metrics/native-metrics-provider.d.ts +48 -0
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +73 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +922 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +914 -0
- package/dist/builtins/platform-probe/hardware-decode-accel-probe.d.ts +37 -0
- package/dist/builtins/platform-probe/hardware-encoder-probe.d.ts +13 -0
- package/dist/builtins/platform-probe/index.d.ts +22 -0
- package/dist/builtins/platform-probe/index.js +834 -0
- package/dist/builtins/platform-probe/index.mjs +822 -0
- package/dist/builtins/platform-probe/inference-config-resolver.d.ts +29 -0
- package/dist/builtins/platform-probe/intel-accelerators.d.ts +11 -0
- package/dist/builtins/platform-probe/platform-scorer.d.ts +30 -0
- package/dist/builtins/platform-probe/runtime-packages.d.ts +6 -0
- package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts +96 -0
- package/dist/builtins/remote-access-orchestrator/index.d.ts +1 -0
- package/dist/builtins/remote-access-orchestrator/index.js +8 -0
- package/dist/builtins/remote-access-orchestrator/index.mjs +2 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +40 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +214 -0
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +208 -0
- package/dist/builtins/shared/settle-sources.d.ts +22 -0
- package/dist/builtins/snapshot/index.d.ts +2 -0
- package/dist/builtins/snapshot/index.js +494 -0
- package/dist/builtins/snapshot/index.mjs +488 -0
- package/dist/builtins/snapshot/snapshot.addon.d.ts +120 -0
- package/dist/builtins/sqlite-storage/config-store.d.ts +8 -0
- package/dist/builtins/sqlite-storage/device-store.d.ts +23 -0
- package/dist/builtins/sqlite-storage/filesystem-browse-provider.d.ts +25 -0
- package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts +83 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +32 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +396 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +388 -0
- package/dist/builtins/sqlite-storage/index.d.ts +8 -0
- package/dist/builtins/sqlite-storage/index.js +62 -0
- package/dist/builtins/sqlite-storage/index.mjs +49 -0
- package/dist/builtins/sqlite-storage/integration-registry.d.ts +27 -0
- package/dist/builtins/sqlite-storage/path-guard.d.ts +4 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +102 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +14 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +644 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +636 -0
- package/dist/builtins/storage-orchestrator/index.d.ts +6 -0
- package/dist/builtins/storage-orchestrator/index.js +10 -0
- package/dist/builtins/storage-orchestrator/index.mjs +2 -0
- package/dist/builtins/storage-orchestrator/location-store.d.ts +49 -0
- package/dist/builtins/storage-orchestrator/provider-discovery.d.ts +10 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +103 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1138 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1128 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +236 -0
- package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +38 -0
- package/dist/builtins/system-backup/system-backup.service.d.ts +137 -0
- package/dist/builtins/system-config/index.d.ts +1 -0
- package/dist/builtins/system-config/index.js +8 -0
- package/dist/builtins/system-config/index.mjs +2 -0
- package/dist/builtins/system-config/system-config.addon.d.ts +10 -0
- package/dist/builtins/system-config/system-config.addon.js +232 -0
- package/dist/builtins/system-config/system-config.addon.mjs +226 -0
- package/dist/builtins/winston-logging/index.d.ts +3 -0
- package/dist/builtins/winston-logging/index.js +156 -0
- package/dist/builtins/winston-logging/index.mjs +144 -0
- package/dist/builtins/winston-logging/winston-destination.d.ts +21 -0
- package/dist/builtins/winston-logging/winston-logging.addon.d.ts +19 -0
- package/dist/chunk-CNf5ZN-e.mjs +37 -0
- package/dist/chunk-Cek0wNdY.js +64 -0
- package/dist/download/model-download-service.d.ts +41 -0
- package/dist/download/model-downloader.d.ts +31 -0
- package/dist/events/event-bus.d.ts +10 -0
- package/dist/events/system-event-bus.d.ts +14 -0
- package/dist/feature/feature-manager.d.ts +11 -0
- package/dist/formatter-B7qW8bPJ.mjs +162 -0
- package/dist/formatter-DqAKDlvN.js +167 -0
- package/dist/http/authenticated-file-server.d.ts +53 -0
- package/dist/http/data-plane-registry.d.ts +23 -0
- package/dist/http/file-data-plane.d.ts +10 -0
- package/dist/http/reverse-proxy.d.ts +15 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +93485 -0
- package/dist/index.mjs +93179 -0
- package/dist/intel-accelerators-Gg0P5mnl.js +20 -0
- package/dist/intel-accelerators-hGgpZ0pX.mjs +19 -0
- package/dist/kernel/addon-class-resolver.d.ts +4 -0
- package/dist/kernel/addon-engine-manager.d.ts +22 -0
- package/dist/kernel/addon-health-monitor.d.ts +154 -0
- package/dist/kernel/addon-installer.d.ts +208 -0
- package/dist/kernel/addon-loader.d.ts +106 -0
- package/dist/kernel/addon-manifest.d.ts +77 -0
- package/dist/kernel/capability-handle.d.ts +46 -0
- package/dist/kernel/capability-registry.d.ts +412 -0
- package/dist/kernel/config-manager.d.ts +212 -0
- package/dist/kernel/config-schema.d.ts +93 -0
- package/dist/kernel/custom-action-registry.d.ts +23 -0
- package/dist/kernel/deps/addon-deps-manager.d.ts +19 -0
- package/dist/kernel/deps/manifest-native-deps.d.ts +25 -0
- package/dist/kernel/deps/manifest-python-deps.d.ts +20 -0
- package/dist/kernel/device-registry.d.ts +29 -0
- package/dist/kernel/fs-utils.d.ts +41 -0
- package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +19 -0
- package/dist/kernel/hwaccel/hwaccel-service.d.ts +4 -0
- package/dist/kernel/index.d.ts +74 -0
- package/dist/kernel/infra-capabilities.d.ts +13 -0
- package/dist/kernel/moleculer/addon-context-factory.d.ts +91 -0
- package/dist/kernel/moleculer/addon-data-plane-facility.d.ts +19 -0
- package/dist/kernel/moleculer/addon-runner.d.ts +1 -0
- package/dist/kernel/moleculer/addon-service-factory.d.ts +50 -0
- package/dist/kernel/moleculer/broker-factory.d.ts +50 -0
- package/dist/kernel/moleculer/cap-usage-registry.d.ts +46 -0
- package/dist/kernel/moleculer/capabilities-access.d.ts +21 -0
- package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +46 -0
- package/dist/kernel/moleculer/child-cap-dispatch.d.ts +20 -0
- package/dist/kernel/moleculer/cluster-secret.d.ts +15 -0
- package/dist/kernel/moleculer/core-cap-service.d.ts +50 -0
- package/dist/kernel/moleculer/crash-supervisor.d.ts +50 -0
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +79 -0
- package/dist/kernel/moleculer/event-bus-core.d.ts +53 -0
- package/dist/kernel/moleculer/event-bus.d.ts +53 -0
- package/dist/kernel/moleculer/hub-log-forwarder.d.ts +36 -0
- package/dist/kernel/moleculer/hub-service.d.ts +35 -0
- package/dist/kernel/moleculer/node-registry.d.ts +126 -0
- package/dist/kernel/moleculer/process-context.d.ts +4 -0
- package/dist/kernel/moleculer/process-service.d.ts +72 -0
- package/dist/kernel/moleculer/provider-registry.d.ts +28 -0
- package/dist/kernel/moleculer/readiness-context.d.ts +62 -0
- package/dist/kernel/moleculer/readiness-service.d.ts +7 -0
- package/dist/kernel/moleculer/register-node-client.d.ts +35 -0
- package/dist/kernel/moleculer/remote-logger.d.ts +43 -0
- package/dist/kernel/moleculer/resilient-cap-call.d.ts +28 -0
- package/dist/kernel/moleculer/stream-probe-service.d.ts +9 -0
- package/dist/kernel/moleculer/trpc-links.d.ts +189 -0
- package/dist/kernel/moleculer/typed-array-serde.d.ts +25 -0
- package/dist/kernel/moleculer/worker-device-restore.d.ts +10 -0
- package/dist/kernel/provider-kind-drift.d.ts +12 -0
- package/dist/kernel/restart-coordinator.d.ts +90 -0
- package/dist/kernel/storage-location-registry.d.ts +40 -0
- package/dist/kernel/transport/cap-action-name.d.ts +100 -0
- package/dist/kernel/transport/cap-route-resolver.d.ts +148 -0
- package/dist/kernel/transport/cap-route.d.ts +148 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +136 -0
- package/dist/kernel/transport/create-local-transport.d.ts +7 -0
- package/dist/kernel/transport/frame-codec.d.ts +7 -0
- package/dist/kernel/transport/index.d.ts +27 -0
- package/dist/kernel/transport/local-child-client.d.ts +136 -0
- package/dist/kernel/transport/local-child-registry.d.ts +179 -0
- package/dist/kernel/transport/local-endpoint-path.d.ts +6 -0
- package/dist/kernel/transport/local-transport.d.ts +46 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +75 -0
- package/dist/kernel/transport/socket-channel.d.ts +27 -0
- package/dist/kernel/transport/uds-event-bridge.d.ts +36 -0
- package/dist/kernel/transport/uds-event-bus.d.ts +22 -0
- package/dist/kernel/transport/uds-local-transport.d.ts +18 -0
- package/dist/kernel/transport/uds-log-ingest.d.ts +28 -0
- package/dist/kernel/transport/uds-logger.d.ts +44 -0
- package/dist/kernel/utils/ring-buffer.d.ts +15 -0
- package/dist/kernel/workspace-detect.d.ts +9 -0
- package/dist/lifecycle/lifecycle-state-machine.d.ts +28 -0
- package/dist/logging/formatter.d.ts +30 -0
- package/dist/logging/log-manager.d.ts +54 -0
- package/dist/logging/log-ring-buffer.d.ts +47 -0
- package/dist/logging/partitioned-log-buffer.d.ts +35 -0
- package/dist/logging/scoped-logger.d.ts +17 -0
- package/dist/main-DNnMW7Z2.js +9983 -0
- package/dist/main-rtjOwPBR.mjs +9976 -0
- package/dist/manifest-python-deps-D1DbAQEv.js +6724 -0
- package/dist/manifest-python-deps-DZsKTbs1.mjs +6315 -0
- package/dist/network/network-quality.d.ts +11 -0
- package/dist/notification/notification-service.d.ts +37 -0
- package/dist/notification/toast-service.d.ts +22 -0
- package/dist/pipeline/engine-manager-resolver.d.ts +15 -0
- package/dist/pipeline/pipeline-runner.d.ts +8 -0
- package/dist/pipeline/pipeline-validator.d.ts +13 -0
- package/dist/process/resource-monitor.d.ts +11 -0
- package/dist/python/python-env-manager.d.ts +12 -0
- package/dist/repl/interfaces.d.ts +31 -0
- package/dist/repl/repl-engine.d.ts +8 -0
- package/dist/resource-monitor-ClDGFyf6.mjs +57 -0
- package/dist/resource-monitor-IIEanuJt.js +74 -0
- package/dist/settle-sources-Bhsy57y-.js +38 -0
- package/dist/settle-sources-CDtNC8ub.mjs +33 -0
- package/dist/storage/fs-storage-backend.d.ts +40 -0
- package/dist/storage/storage-location-manager.d.ts +23 -0
- package/dist/storage/storage-manager.d.ts +83 -0
- package/dist/tar-BgAEMRBR.js +5434 -0
- package/dist/tar-ByMOPNM0.mjs +5429 -0
- package/dist/tls/cert-manager.d.ts +26 -0
- package/dist/tls/index.d.ts +1 -0
- package/package.json +343 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { BaseAddon, EventCategory, localNetworkCapability } from "@camstack/types";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
//#region src/builtins/local-network/local-network.addon.ts
|
|
4
|
+
/**
|
|
5
|
+
* local-network — hub-only system cap implementation.
|
|
6
|
+
*
|
|
7
|
+
* Wraps `os.networkInterfaces()` with:
|
|
8
|
+
* - Coarse kind classification (lan/wifi/docker/vpn/loopback/other)
|
|
9
|
+
* - Auto-select heuristic for the outbound interface
|
|
10
|
+
* - Periodic poll (30s) + diff to emit `LocalNetworkChanged` events
|
|
11
|
+
* so subscribers can react to DHCP renewals, VPN connect, etc.
|
|
12
|
+
* - `getConnectionEndpoints()` — ranked URL list for SDK clients
|
|
13
|
+
*
|
|
14
|
+
* Does NOT poll the cloudflare-tunnel state directly; the public
|
|
15
|
+
* tunnel hostname (if any) is provided by `network-access` consumers
|
|
16
|
+
* via `NetworkTunnelStarted/Stopped` events on the bus, so the cap
|
|
17
|
+
* stays free of cross-addon dependencies.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Forked `mesh-network` provider addon ids `local-network` pull-reconciles
|
|
21
|
+
* against. `mesh-network` is a COLLECTION cap, so a hub builtin's local
|
|
22
|
+
* registry can't enumerate forked providers — we resolve each by addonId
|
|
23
|
+
* via `getProviderByAddon`. New mesh providers (Headscale, ZeroTier) add
|
|
24
|
+
* their id here.
|
|
25
|
+
*/
|
|
26
|
+
var MESH_PROVIDER_ADDON_IDS = ["tailscale-client"];
|
|
27
|
+
var POLL_INTERVAL_MS = 3e4;
|
|
28
|
+
var LocalNetworkAddon = class extends BaseAddon {
|
|
29
|
+
pollTimer = null;
|
|
30
|
+
lastSnapshotKey = "";
|
|
31
|
+
/** Optional public hostname tracked from `NetworkTunnelStarted`/
|
|
32
|
+
* `Stopped` events on the bus. */
|
|
33
|
+
publicHostname = "";
|
|
34
|
+
/**
|
|
35
|
+
* Mesh-reachable endpoint (Tailscale MagicDNS / 100.x), learned from
|
|
36
|
+
* the `MeshNetworkChanged` event-tail and refreshed by a pull-reconcile
|
|
37
|
+
* before each `getConnectionEndpoints` build (events are lossy — D8).
|
|
38
|
+
* `null` when no mesh provider is joined.
|
|
39
|
+
*/
|
|
40
|
+
meshEndpoint = null;
|
|
41
|
+
constructor() {
|
|
42
|
+
super({
|
|
43
|
+
allowedAddresses: [],
|
|
44
|
+
bootSeeded: false
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async onInitialize() {
|
|
48
|
+
if (!this.config.bootSeeded) {
|
|
49
|
+
const seed = autoSeedAllowlist(this.enumerate());
|
|
50
|
+
await this.updateGlobalSettings({
|
|
51
|
+
allowedAddresses: seed,
|
|
52
|
+
bootSeeded: true
|
|
53
|
+
});
|
|
54
|
+
this.ctx.logger.info("local-network: first-boot auto-seed", { meta: { addresses: seed } });
|
|
55
|
+
}
|
|
56
|
+
const provider = {
|
|
57
|
+
list: async () => ({
|
|
58
|
+
interfaces: this.enumerate(),
|
|
59
|
+
probedAt: Date.now()
|
|
60
|
+
}),
|
|
61
|
+
getPreferred: async () => pickPreferred(applyAllowlist(this.enumerate(), this.config.allowedAddresses)),
|
|
62
|
+
getConnectionEndpoints: async (input) => {
|
|
63
|
+
const includeLoopback = input.includeLoopback ?? true;
|
|
64
|
+
const ipv4Only = input.ipv4Only ?? false;
|
|
65
|
+
const scheme = input.scheme ?? "http";
|
|
66
|
+
const allow = this.config.allowedAddresses;
|
|
67
|
+
const interfaces = applyAllowlist(this.enumerate(), allow);
|
|
68
|
+
await this.reconcileMeshEndpoint();
|
|
69
|
+
return { endpoints: buildEndpoints(interfaces, input.port, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint) };
|
|
70
|
+
},
|
|
71
|
+
getAllowedAddresses: async () => ({ addresses: this.config.allowedAddresses }),
|
|
72
|
+
resetAllowlistToBestMatch: async () => {
|
|
73
|
+
const seed = autoSeedAllowlist(this.enumerate());
|
|
74
|
+
await this.updateGlobalSettings({
|
|
75
|
+
allowedAddresses: seed,
|
|
76
|
+
bootSeeded: true
|
|
77
|
+
});
|
|
78
|
+
this.ctx.logger.info("local-network: allowlist reset to auto-seed", { meta: { count: seed.length } });
|
|
79
|
+
return { addresses: seed };
|
|
80
|
+
},
|
|
81
|
+
setAllowedAddresses: async ({ addresses }) => {
|
|
82
|
+
const known = new Set(this.enumerate().map((i) => i.address));
|
|
83
|
+
const cleaned = [...new Set(addresses)].filter((a) => known.has(a));
|
|
84
|
+
await this.updateGlobalSettings({ allowedAddresses: cleaned });
|
|
85
|
+
this.ctx.logger.info("local-network: allowlist updated", { meta: {
|
|
86
|
+
count: cleaned.length,
|
|
87
|
+
dropped: addresses.length - cleaned.length
|
|
88
|
+
} });
|
|
89
|
+
return { success: true };
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
this.lastSnapshotKey = snapshotKey(this.enumerate());
|
|
93
|
+
this.pollTimer = setInterval(() => this.detectChanges(), POLL_INTERVAL_MS);
|
|
94
|
+
this.pollTimer.unref?.();
|
|
95
|
+
this.ctx.eventBus?.subscribe({ category: EventCategory.NetworkTunnelStarted }, (event) => {
|
|
96
|
+
const data = event.data ?? {};
|
|
97
|
+
if (typeof data.url === "string") try {
|
|
98
|
+
const hostname = new URL(data.url).hostname;
|
|
99
|
+
if (hostname && !hostname.endsWith(".placeholder") && !hostname.startsWith("pending.")) this.setPublicHostname(hostname);
|
|
100
|
+
} catch {}
|
|
101
|
+
});
|
|
102
|
+
this.ctx.eventBus?.subscribe({ category: EventCategory.NetworkTunnelStopped }, () => this.setPublicHostname(""));
|
|
103
|
+
this.ctx.eventBus?.subscribe({ category: EventCategory.MeshNetworkChanged }, (event) => {
|
|
104
|
+
const data = event.data ?? {};
|
|
105
|
+
const host = typeof data.host === "string" ? data.host : "";
|
|
106
|
+
const port = typeof data.port === "number" ? data.port : 0;
|
|
107
|
+
this.setMeshEndpoint(host && port > 0 ? {
|
|
108
|
+
host,
|
|
109
|
+
port
|
|
110
|
+
} : null);
|
|
111
|
+
});
|
|
112
|
+
this.ctx.logger.info("local-network initialized", { meta: { interfaceCount: this.enumerate().length } });
|
|
113
|
+
return [{
|
|
114
|
+
capability: localNetworkCapability,
|
|
115
|
+
provider
|
|
116
|
+
}];
|
|
117
|
+
}
|
|
118
|
+
async onShutdown() {
|
|
119
|
+
if (this.pollTimer) {
|
|
120
|
+
clearInterval(this.pollTimer);
|
|
121
|
+
this.pollTimer = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Other hub addons (e.g. cloudflare-tunnel) signal the active public
|
|
126
|
+
* FQDN by emitting `NetworkTunnelStarted` on the bus — handled in
|
|
127
|
+
* `onInitialize`. This setter exists for tests + future direct
|
|
128
|
+
* callers; cleared by passing an empty string.
|
|
129
|
+
*/
|
|
130
|
+
setPublicHostname(hostname) {
|
|
131
|
+
if (this.publicHostname === hostname) return;
|
|
132
|
+
this.publicHostname = hostname;
|
|
133
|
+
this.ctx.logger.info("local-network: public hostname updated", { meta: { hostname: hostname || "(cleared)" } });
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Replace the cached mesh endpoint. Set from the `MeshNetworkChanged`
|
|
137
|
+
* event-tail + the pull-reconcile; exposed for tests + future direct
|
|
138
|
+
* callers. Pass `null` to clear (mesh left / no provider joined).
|
|
139
|
+
*/
|
|
140
|
+
setMeshEndpoint(endpoint) {
|
|
141
|
+
const prev = this.meshEndpoint;
|
|
142
|
+
if (prev?.host === endpoint?.host && prev?.port === endpoint?.port) return;
|
|
143
|
+
this.meshEndpoint = endpoint;
|
|
144
|
+
this.ctx.logger.info("local-network: mesh endpoint updated", { meta: {
|
|
145
|
+
host: endpoint?.host || "(cleared)",
|
|
146
|
+
port: endpoint?.port ?? 0
|
|
147
|
+
} });
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Refresh the cached mesh endpoint from the authoritative forked
|
|
151
|
+
* `mesh-network` provider(s). `mesh-network` is a COLLECTION cap on a
|
|
152
|
+
* forked addon, so the hub builtin can't see it via `ctx.api` /
|
|
153
|
+
* `getCollectionEntries` — we resolve each provider by addonId through
|
|
154
|
+
* the hub-side `capabilityRegistry.getProviderByAddon` resolver (the
|
|
155
|
+
* same mechanism `device-manager` uses for cross-process exporter
|
|
156
|
+
* contributions). Best-effort: a provider that's absent / errors /
|
|
157
|
+
* not joined simply contributes nothing.
|
|
158
|
+
*/
|
|
159
|
+
async reconcileMeshEndpoint() {
|
|
160
|
+
const registry = this.ctx.kernel.capabilityRegistry;
|
|
161
|
+
if (!registry) return;
|
|
162
|
+
for (const addonId of MESH_PROVIDER_ADDON_IDS) {
|
|
163
|
+
const provider = registry.getProviderByAddon("mesh-network", addonId);
|
|
164
|
+
if (!provider) continue;
|
|
165
|
+
try {
|
|
166
|
+
const status = await provider.getStatus();
|
|
167
|
+
if (!status.joined) {
|
|
168
|
+
this.setMeshEndpoint(null);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const host = status.magicDnsHostname || status.meshIp;
|
|
172
|
+
const port = (status.endpoints.find((e) => e.id === "magicdns") ?? status.endpoints.find((e) => e.id === "mesh-ipv4"))?.port ?? 0;
|
|
173
|
+
this.setMeshEndpoint(host && port > 0 ? {
|
|
174
|
+
host,
|
|
175
|
+
port
|
|
176
|
+
} : null);
|
|
177
|
+
return;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
this.ctx.logger.debug("local-network: mesh reconcile skipped", { meta: {
|
|
180
|
+
addonId,
|
|
181
|
+
error: err instanceof Error ? err.message : String(err)
|
|
182
|
+
} });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
enumerate() {
|
|
187
|
+
return enumerateOsInterfaces(os.networkInterfaces());
|
|
188
|
+
}
|
|
189
|
+
detectChanges() {
|
|
190
|
+
const interfaces = this.enumerate();
|
|
191
|
+
const key = snapshotKey(interfaces);
|
|
192
|
+
if (key === this.lastSnapshotKey) return;
|
|
193
|
+
this.ctx.logger.info("local-network: interface set changed", { meta: {
|
|
194
|
+
count: interfaces.length,
|
|
195
|
+
key
|
|
196
|
+
} });
|
|
197
|
+
this.lastSnapshotKey = key;
|
|
198
|
+
this.ctx.eventBus?.emit({
|
|
199
|
+
id: `local-network-changed-${Date.now()}`,
|
|
200
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
201
|
+
source: {
|
|
202
|
+
type: "core",
|
|
203
|
+
id: "local-network"
|
|
204
|
+
},
|
|
205
|
+
category: EventCategory.LocalNetworkChanged,
|
|
206
|
+
data: { count: interfaces.length }
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
function enumerateOsInterfaces(ifaces) {
|
|
211
|
+
const raw = [];
|
|
212
|
+
for (const [name, addrs] of Object.entries(ifaces)) {
|
|
213
|
+
if (!addrs) continue;
|
|
214
|
+
for (const a of addrs) {
|
|
215
|
+
if (a.family !== "IPv4" && a.family !== "IPv6") continue;
|
|
216
|
+
raw.push({
|
|
217
|
+
name,
|
|
218
|
+
family: a.family,
|
|
219
|
+
address: a.address,
|
|
220
|
+
cidr: a.cidr ?? "",
|
|
221
|
+
netmask: a.netmask,
|
|
222
|
+
internal: a.internal,
|
|
223
|
+
mac: a.mac
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const classified = raw.map((r) => {
|
|
228
|
+
const kind = classifyKind(r.name, r.address, r.internal);
|
|
229
|
+
const reachableKind = kind === "lan" || kind === "wifi";
|
|
230
|
+
const plausible = !r.internal && reachableKind && isPlausibleAutoSeed(r.address, r.family);
|
|
231
|
+
const plausibleReason = plausible ? "" : explainNonPlausible({
|
|
232
|
+
kind,
|
|
233
|
+
family: r.family,
|
|
234
|
+
address: r.address,
|
|
235
|
+
internal: r.internal
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
name: r.name,
|
|
239
|
+
family: r.family,
|
|
240
|
+
address: r.address,
|
|
241
|
+
cidr: r.cidr,
|
|
242
|
+
netmask: r.netmask,
|
|
243
|
+
internal: r.internal,
|
|
244
|
+
mac: r.mac,
|
|
245
|
+
kind,
|
|
246
|
+
preferred: false,
|
|
247
|
+
plausible,
|
|
248
|
+
plausibleReason
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
const preferred = pickPreferred(classified);
|
|
252
|
+
return classified.map((iface) => ({
|
|
253
|
+
...iface,
|
|
254
|
+
preferred: preferred !== null && iface.name === preferred.name && iface.address === preferred.address
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Filter the interface list by the operator's allowlist. Empty
|
|
259
|
+
* allowlist = no-op (every interface passes); otherwise only entries
|
|
260
|
+
* whose `address` matches an allowlist entry remain. Loopback always
|
|
261
|
+
* survives so the SDK keeps `127.0.0.1` as the last-resort fallback.
|
|
262
|
+
*/
|
|
263
|
+
function applyAllowlist(interfaces, allowed) {
|
|
264
|
+
if (allowed.length === 0) return interfaces;
|
|
265
|
+
const set = new Set(allowed);
|
|
266
|
+
return interfaces.filter((i) => i.kind === "loopback" || set.has(i.address));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Rank interfaces and pick the auto-selected outbound one. See the
|
|
270
|
+
* cap's `getPreferred` doc for the heuristic.
|
|
271
|
+
*/
|
|
272
|
+
function pickPreferred(interfaces) {
|
|
273
|
+
const candidates = interfaces.filter((i) => !i.internal && i.family === "IPv4" && !i.address.startsWith("169.254.") && i.kind !== "loopback");
|
|
274
|
+
if (candidates.length === 0) return null;
|
|
275
|
+
const kindRank = {
|
|
276
|
+
lan: 1,
|
|
277
|
+
wifi: 2,
|
|
278
|
+
vpn: 3,
|
|
279
|
+
docker: 4,
|
|
280
|
+
other: 5,
|
|
281
|
+
loopback: 99
|
|
282
|
+
};
|
|
283
|
+
return [...candidates].toSorted((a, b) => {
|
|
284
|
+
const ra = kindRank[a.kind];
|
|
285
|
+
const rb = kindRank[b.kind];
|
|
286
|
+
if (ra !== rb) return ra - rb;
|
|
287
|
+
return prefixLen(b.netmask) - prefixLen(a.netmask);
|
|
288
|
+
})[0] ?? null;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Build the ordered candidate URL list. Priority schema:
|
|
292
|
+
* 0 — preferred LAN IPv4
|
|
293
|
+
* 10+ — other LAN IPv4
|
|
294
|
+
* 100 — public tunnel hostname (always HTTPS)
|
|
295
|
+
* 150 — mesh endpoint (Tailscale MagicDNS / 100.x, always HTTPS)
|
|
296
|
+
* 200+ — LAN IPv6
|
|
297
|
+
* 1000 — loopback (last resort)
|
|
298
|
+
*
|
|
299
|
+
* The mesh endpoint ranks just below a true public funnel (a Funnel is
|
|
300
|
+
* reachable by ANY client; the mesh needs the peer on the same tailnet)
|
|
301
|
+
* but above the IPv6 / loopback fallbacks — giving remote, mesh-joined
|
|
302
|
+
* clients a working candidate when no public tunnel is up.
|
|
303
|
+
*
|
|
304
|
+
* `scheme` controls LAN + loopback URLs. Browsers running over HTTPS
|
|
305
|
+
* block `http://` candidates as mixed content, so callers loaded over
|
|
306
|
+
* HTTPS should pass `scheme: 'https'` even when probing a LAN IP — the
|
|
307
|
+
* hub's cert manager already issues a SAN-multi cert covering local
|
|
308
|
+
* interfaces. The public tunnel + mesh endpoint always emit `https://`
|
|
309
|
+
* (the tunnel edge / the hub itself terminates TLS).
|
|
310
|
+
*/
|
|
311
|
+
function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostname, scheme = "http", meshEndpoint = null) {
|
|
312
|
+
const out = [];
|
|
313
|
+
let priority = 0;
|
|
314
|
+
const preferred = pickPreferred(interfaces);
|
|
315
|
+
const emit = (iface, kind, label, baseUrl, pri) => {
|
|
316
|
+
out.push({
|
|
317
|
+
label,
|
|
318
|
+
baseUrl,
|
|
319
|
+
kind,
|
|
320
|
+
interfaceKind: iface.kind,
|
|
321
|
+
plausible: iface.plausible,
|
|
322
|
+
plausibleReason: iface.plausibleReason,
|
|
323
|
+
priority: pri
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
if (preferred) emit(preferred, "lan-ipv4", `${formatKind(preferred.kind)} — ${preferred.name}`, `${scheme}://${preferred.address}:${port}`, priority++);
|
|
327
|
+
for (const iface of interfaces) {
|
|
328
|
+
if (iface.internal || iface.family !== "IPv4") continue;
|
|
329
|
+
if (iface.kind === "loopback") continue;
|
|
330
|
+
if (preferred && iface.name === preferred.name && iface.address === preferred.address) continue;
|
|
331
|
+
if (iface.address.startsWith("169.254.")) continue;
|
|
332
|
+
emit(iface, "lan-ipv4", `${formatKind(iface.kind)} — ${iface.name}`, `${scheme}://${iface.address}:${port}`, 10 + priority++);
|
|
333
|
+
}
|
|
334
|
+
if (publicHostname) out.push({
|
|
335
|
+
label: "Public tunnel",
|
|
336
|
+
baseUrl: `https://${publicHostname}`,
|
|
337
|
+
kind: "public",
|
|
338
|
+
interfaceKind: "public",
|
|
339
|
+
plausible: true,
|
|
340
|
+
plausibleReason: "",
|
|
341
|
+
priority: 100
|
|
342
|
+
});
|
|
343
|
+
if (meshEndpoint && meshEndpoint.host) out.push({
|
|
344
|
+
label: "Mesh (Tailscale)",
|
|
345
|
+
baseUrl: `https://${meshEndpoint.host}:${meshEndpoint.port}`,
|
|
346
|
+
kind: "public",
|
|
347
|
+
interfaceKind: "vpn",
|
|
348
|
+
plausible: true,
|
|
349
|
+
plausibleReason: "",
|
|
350
|
+
priority: 150
|
|
351
|
+
});
|
|
352
|
+
if (!ipv4Only) {
|
|
353
|
+
let v6prio = 200;
|
|
354
|
+
for (const iface of interfaces) {
|
|
355
|
+
if (iface.internal || iface.family !== "IPv6") continue;
|
|
356
|
+
if (iface.kind === "loopback") continue;
|
|
357
|
+
if (iface.address.startsWith("fe80:")) continue;
|
|
358
|
+
emit(iface, "lan-ipv6", `${formatKind(iface.kind)} — ${iface.name} (IPv6)`, `${scheme}://[${iface.address}]:${port}`, v6prio++);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (includeLoopback) out.push({
|
|
362
|
+
label: "Loopback",
|
|
363
|
+
baseUrl: `${scheme}://127.0.0.1:${port}`,
|
|
364
|
+
kind: "loopback",
|
|
365
|
+
interfaceKind: "loopback",
|
|
366
|
+
plausible: false,
|
|
367
|
+
plausibleReason: "Loopback — last-resort fallback when no other endpoint responds.",
|
|
368
|
+
priority: 1e3
|
|
369
|
+
});
|
|
370
|
+
return out.toSorted((a, b) => a.priority - b.priority);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* First-boot heuristic: which addresses should the allowlist start
|
|
374
|
+
* with? Includes LAN + Wi-Fi IPv4 addresses + plausible IPv6:
|
|
375
|
+
*
|
|
376
|
+
* - **IPv4**: skip link-local (`169.254.*`), keep the rest.
|
|
377
|
+
* - **IPv6**: skip link-local (`fe80::*`), unspecified, and
|
|
378
|
+
* multicast. Keep ULAs (`fc00::/7` → `fc??:` / `fd??:`) and Global
|
|
379
|
+
* Unicast addresses (`2000::/3` → `2???`/`3???`). Privacy-extension
|
|
380
|
+
* temporary addresses get included by default; the operator can
|
|
381
|
+
* prune them from the Network Addresses tab if the rotating IPs
|
|
382
|
+
* become a nuisance.
|
|
383
|
+
*
|
|
384
|
+
* Skips docker/vpn/loopback/other entirely — those stay opt-in.
|
|
385
|
+
*/
|
|
386
|
+
function autoSeedAllowlist(interfaces) {
|
|
387
|
+
return [...new Set(interfaces.filter((i) => !i.internal && (i.kind === "lan" || i.kind === "wifi") && isPlausibleAutoSeed(i.address, i.family)).map((i) => i.address))];
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Per-interface tooltip text surfaced on the "Unlikely usable" badge.
|
|
391
|
+
* Server-side so the UI doesn't re-derive the rationale (single source
|
|
392
|
+
* of truth). Returns `''` for plausible entries; the addon overlays
|
|
393
|
+
* this on the `LocalInterface.plausibleReason` field.
|
|
394
|
+
*/
|
|
395
|
+
function explainNonPlausible(input) {
|
|
396
|
+
if (input.internal) return "Internal interface (loopback) — not reachable from clients.";
|
|
397
|
+
if (input.kind === "docker") return "Docker bridge — only reachable from inside the container network.";
|
|
398
|
+
if (input.kind === "vpn") return "VPN tunnel — only reachable while the VPN is connected.";
|
|
399
|
+
if (input.kind === "other") return "Unrecognised interface kind — verify reachability before pinning.";
|
|
400
|
+
if (input.family === "IPv6") {
|
|
401
|
+
const a = input.address.toLowerCase();
|
|
402
|
+
if (a.startsWith("fe80:")) return "IPv6 link-local — only reachable on the same link, not routed.";
|
|
403
|
+
if (a.startsWith("ff")) return "IPv6 multicast — not a unicast address.";
|
|
404
|
+
if (a === "::" || a === "::1") return "IPv6 loopback / unspecified — not a public address.";
|
|
405
|
+
return "IPv6 address outside the ULA / GUA ranges — verify before pinning.";
|
|
406
|
+
}
|
|
407
|
+
if (input.address.startsWith("169.254.")) return "IPv4 link-local (RFC 3927) — only valid when DHCP fails.";
|
|
408
|
+
return "Address looks unusual for client traffic — verify before pinning.";
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Per-address gate used by `autoSeedAllowlist`. Exposed for tests so
|
|
412
|
+
* we can pin every classification rule without standing up the addon.
|
|
413
|
+
*/
|
|
414
|
+
function isPlausibleAutoSeed(address, family) {
|
|
415
|
+
if (family === "IPv4") {
|
|
416
|
+
if (address.startsWith("169.254.")) return false;
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
const a = address.toLowerCase();
|
|
420
|
+
if (a === "::" || a === "::1") return false;
|
|
421
|
+
if (a.startsWith("fe80:")) return false;
|
|
422
|
+
if (a.startsWith("ff")) return false;
|
|
423
|
+
if (/^f[cd][0-9a-f]{0,2}:/.test(a)) return true;
|
|
424
|
+
if (/^[23][0-9a-f]{0,3}:/.test(a)) return true;
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
function classifyKind(name, address, internal) {
|
|
428
|
+
if (internal || name === "lo" || name.startsWith("lo")) return "loopback";
|
|
429
|
+
const n = name.toLowerCase();
|
|
430
|
+
if (n.startsWith("docker") || n.startsWith("br-") || n.startsWith("veth")) return "docker";
|
|
431
|
+
if (n.startsWith("tun") || n.startsWith("utun") || n.startsWith("wg") || n.startsWith("tap")) return "vpn";
|
|
432
|
+
if (n.startsWith("wlan") || n.startsWith("wlp") || n.startsWith("wlx")) return "wifi";
|
|
433
|
+
if (n.startsWith("eth") || /^en\d+$/.test(n)) {
|
|
434
|
+
if (process.platform === "darwin" && /^en[1-9]\d*$/.test(n)) return "wifi";
|
|
435
|
+
return "lan";
|
|
436
|
+
}
|
|
437
|
+
if (address === "127.0.0.1" || address === "::1") return "loopback";
|
|
438
|
+
return "other";
|
|
439
|
+
}
|
|
440
|
+
/** Convert an IPv4/IPv6 netmask string to its prefix length (CIDR). */
|
|
441
|
+
function prefixLen(netmask) {
|
|
442
|
+
if (!netmask) return 0;
|
|
443
|
+
if (netmask.includes(":")) {
|
|
444
|
+
let bits = 0;
|
|
445
|
+
for (const group of netmask.split(":")) {
|
|
446
|
+
if (!group) continue;
|
|
447
|
+
const n = parseInt(group, 16);
|
|
448
|
+
if (!Number.isFinite(n)) break;
|
|
449
|
+
for (let mask = 32768; mask; mask >>= 1) if (n & mask) bits++;
|
|
450
|
+
else return bits;
|
|
451
|
+
}
|
|
452
|
+
return bits;
|
|
453
|
+
}
|
|
454
|
+
let bits = 0;
|
|
455
|
+
for (const part of netmask.split(".")) {
|
|
456
|
+
const n = parseInt(part, 10);
|
|
457
|
+
if (!Number.isFinite(n)) break;
|
|
458
|
+
for (let mask = 128; mask; mask >>= 1) if (n & mask) bits++;
|
|
459
|
+
else return bits;
|
|
460
|
+
}
|
|
461
|
+
return bits;
|
|
462
|
+
}
|
|
463
|
+
function snapshotKey(interfaces) {
|
|
464
|
+
return [...interfaces].map((i) => `${i.name}|${i.family}|${i.address}|${i.netmask}`).toSorted().join("\n");
|
|
465
|
+
}
|
|
466
|
+
function formatKind(kind) {
|
|
467
|
+
switch (kind) {
|
|
468
|
+
case "lan": return "LAN";
|
|
469
|
+
case "wifi": return "Wi-Fi";
|
|
470
|
+
case "vpn": return "VPN";
|
|
471
|
+
case "docker": return "Docker";
|
|
472
|
+
case "loopback": return "Loopback";
|
|
473
|
+
case "other": return "Other";
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
//#endregion
|
|
477
|
+
export { LocalNetworkAddon, applyAllowlist, autoSeedAllowlist, buildEndpoints, classifyKind, enumerateOsInterfaces, explainNonPlausible, isPlausibleAutoSeed, pickPreferred, prefixLen };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SystemResourceSnapshot, MemoryInfo, DiskSpaceInfo, MetricsGpuInfo, PidResourceStats } from '@camstack/types';
|
|
2
|
+
interface SamplingDiagnostics {
|
|
3
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
4
|
+
}
|
|
5
|
+
export declare class NativeMetricsProvider {
|
|
6
|
+
readonly id = "native";
|
|
7
|
+
private cachedSnapshot;
|
|
8
|
+
private samplingTimer;
|
|
9
|
+
private macMemTimer;
|
|
10
|
+
private prevCpu;
|
|
11
|
+
private diagnostics;
|
|
12
|
+
private consecutiveSampleErrors;
|
|
13
|
+
constructor();
|
|
14
|
+
/**
|
|
15
|
+
* Optional diagnostics sink for sampling errors. The first failure and
|
|
16
|
+
* every Nth failure thereafter are reported so a permanently-broken
|
|
17
|
+
* collector doesn't spam logs but is still observable.
|
|
18
|
+
*/
|
|
19
|
+
setDiagnostics(diag: SamplingDiagnostics): void;
|
|
20
|
+
startSampling(intervalMs: number): void;
|
|
21
|
+
private reportSampleError;
|
|
22
|
+
stopSampling(): void;
|
|
23
|
+
collectSnapshot(): Promise<SystemResourceSnapshot>;
|
|
24
|
+
getCached(): Promise<SystemResourceSnapshot | null>;
|
|
25
|
+
getCurrent(): Promise<{
|
|
26
|
+
cpuPercent: number;
|
|
27
|
+
memoryPercent: number;
|
|
28
|
+
memoryUsedMB: number;
|
|
29
|
+
memoryTotalMB: number;
|
|
30
|
+
diskPercent?: number;
|
|
31
|
+
temperature?: number;
|
|
32
|
+
gpuPercent?: number;
|
|
33
|
+
gpuMemoryPercent?: number;
|
|
34
|
+
}>;
|
|
35
|
+
getDiskSpace(input: {
|
|
36
|
+
dirPath: string;
|
|
37
|
+
}): Promise<DiskSpaceInfo>;
|
|
38
|
+
getGpuInfo(): Promise<MetricsGpuInfo | null>;
|
|
39
|
+
getCpuTemperature(): Promise<number | null>;
|
|
40
|
+
getProcessStats(input: {
|
|
41
|
+
pids: number[];
|
|
42
|
+
}): Promise<PidResourceStats[]>;
|
|
43
|
+
private sampleCpu;
|
|
44
|
+
/** Cached macOS memory (set by sampleMacMemory timer). */
|
|
45
|
+
_cachedMacMem: MemoryInfo | null;
|
|
46
|
+
private getMemoryInfo;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ProviderRegistration, BaseAddon } from '@camstack/types';
|
|
2
|
+
interface NativeMetricsConfig {
|
|
3
|
+
readonly samplingIntervalMs: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Native metrics — CPU, memory, disk usage sampling.
|
|
7
|
+
* Settings appear under Cluster → NodeDetail → Settings.
|
|
8
|
+
*/
|
|
9
|
+
export default class NativeMetricsAddon extends BaseAddon<NativeMetricsConfig> {
|
|
10
|
+
private provider;
|
|
11
|
+
private startedAtMs;
|
|
12
|
+
private snapshotTimer;
|
|
13
|
+
/**
|
|
14
|
+
* Snapshot-equality cache for the resources + processes emit.
|
|
15
|
+
* Stores the coarsened JSON and timestamp; a tick where the
|
|
16
|
+
* coarsened payload matches the cache (and the heartbeat hasn't
|
|
17
|
+
* elapsed) is skipped.
|
|
18
|
+
*/
|
|
19
|
+
private lastResourcesEmit;
|
|
20
|
+
private lastProcessesEmit;
|
|
21
|
+
constructor();
|
|
22
|
+
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
23
|
+
protected onShutdown(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Emit one `metrics.node-resources-snapshot` + one
|
|
26
|
+
* `metrics.node-processes-snapshot` for this node. UI consumers
|
|
27
|
+
* subscribe and read state directly from the payload.
|
|
28
|
+
*/
|
|
29
|
+
private emitMetricsSnapshots;
|
|
30
|
+
protected onConfigChanged(): Promise<void>;
|
|
31
|
+
private listWorkerInstances;
|
|
32
|
+
private listAddonInstances;
|
|
33
|
+
private getAddonStats;
|
|
34
|
+
/**
|
|
35
|
+
* Walk the OS process table and classify each camstack-shaped process.
|
|
36
|
+
*
|
|
37
|
+
* Classification (ancestry-driven, NOT pattern-driven):
|
|
38
|
+
* - root — the current node's own pid (`process.pid`).
|
|
39
|
+
* - managed — pid is registered in the kernel's `$process.list`
|
|
40
|
+
* (forked addon worker spawned by this hub).
|
|
41
|
+
* - system — ancestry walk crosses a SUPERVISOR_BOUNDARY_RE match
|
|
42
|
+
* (tsx-watch launcher, agent CLI, concurrently, vite,
|
|
43
|
+
* npm exec wrapper). The process belongs to the dev
|
|
44
|
+
* tree even if not in `$process.list`. NEVER killable.
|
|
45
|
+
* - ghost — ancestry walk reaches `ppid=1` without crossing any
|
|
46
|
+
* supervisor boundary AND the parent isn't visible in
|
|
47
|
+
* `ps`. A truly orphaned camstack-shaped process. The
|
|
48
|
+
* ONLY classification that's eligible for kill.
|
|
49
|
+
*
|
|
50
|
+
* Old pattern-only ghost detection produced false positives: every
|
|
51
|
+
* monorepo-path process matched CAMSTACK_CMD_RE, ancestry walk
|
|
52
|
+
* stopping at ppid=hub returned false-positive ghosts whenever a
|
|
53
|
+
* concurrently sibling sat above hub. Ancestry-driven classification
|
|
54
|
+
* fixes that.
|
|
55
|
+
*/
|
|
56
|
+
private listNodeProcesses;
|
|
57
|
+
/**
|
|
58
|
+
* Send SIGTERM / SIGKILL to a pid. Refuses pids that don't appear in
|
|
59
|
+
* `listNodeProcesses()` to prevent arbitrary system kills — a dedicated
|
|
60
|
+
* admin-path for resurrected zombies, not a generic shell replacement.
|
|
61
|
+
*
|
|
62
|
+
* `root`-classified pids (the running launcher / agent CLI / hub itself)
|
|
63
|
+
* are also refused: killing them tears down the whole node and the
|
|
64
|
+
* operator's intent is almost always to nuke a leaked child, not the
|
|
65
|
+
* supervisor that keeps the rest alive. Process restart goes through
|
|
66
|
+
* the dedicated `$process.restart` action, not this kill API.
|
|
67
|
+
*/
|
|
68
|
+
private killProcess;
|
|
69
|
+
/** Raw `ps` scan returning every pid + command + resource stats. */
|
|
70
|
+
private runPs;
|
|
71
|
+
protected globalSettingsSchema(): import('@camstack/types').ConfigUISchema;
|
|
72
|
+
}
|
|
73
|
+
export {};
|