@camstack/system 1.0.7 → 1.1.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/dist/addon-runner.js +3 -3
- package/dist/addon-runner.mjs +3 -3
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +8 -8
- package/dist/builtins/device-manager/device-manager.addon.mjs +8 -8
- package/dist/builtins/platform-probe/index.js +27 -139
- package/dist/builtins/platform-probe/index.mjs +28 -140
- package/dist/builtins/platform-probe/platform-scorer.d.ts +17 -10
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
- package/dist/index.js +153 -78
- package/dist/index.mjs +153 -79
- package/dist/kernel/addon-installer.d.ts +20 -0
- package/dist/kernel/config-manager.d.ts +4 -4
- package/dist/kernel/fs-utils.d.ts +16 -6
- package/dist/kernel/index.d.ts +2 -1
- package/dist/kernel/moleculer/addon-deploy-source.d.ts +16 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +10 -0
- package/dist/{manifest-python-deps-eBDj5HEY.js → manifest-python-deps-BWURo7dc.js} +34 -17
- package/dist/{manifest-python-deps-CoJXeb9u.mjs → manifest-python-deps-BcrTzHH_.mjs} +54 -37
- package/dist/{model-download-service-JtVQtbb6.js → model-download-service-1eEOkNeS.js} +35 -5
- package/dist/{model-download-service-C7AjBsX9.mjs → model-download-service-RxAOiYvX.mjs} +35 -5
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { a as ensureModel, c as isModelDownloaded, d as createAuthenticatedFileServer, f as parseRangeHeader, i as downloadModel, l as createFileDataPlaneHandler, m as resolveFilePath, n as deleteModelFromDisk, o as fetchJson, p as parseTokenizedUrl, r as downloadFile, s as getModelFilePath, t as ModelDownloadService, u as contentTypeFor } from "./model-download-service-
|
|
2
|
+
import { a as ensureModel, c as isModelDownloaded, d as createAuthenticatedFileServer, f as parseRangeHeader, i as downloadModel, l as createFileDataPlaneHandler, m as resolveFilePath, n as deleteModelFromDisk, o as fetchJson, p as parseTokenizedUrl, r as downloadFile, s as getModelFilePath, t as ModelDownloadService, u as contentTypeFor } from "./model-download-service-RxAOiYvX.mjs";
|
|
3
3
|
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
|
|
4
4
|
import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
|
|
5
5
|
import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
|
|
@@ -19,7 +19,7 @@ import { LocalAuthAddon, a as require_safe_buffer, i as AuthManager, n as ApiKey
|
|
|
19
19
|
import "./builtins/local-auth/index.mjs";
|
|
20
20
|
import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
|
|
21
21
|
import "./builtins/device-manager/index.mjs";
|
|
22
|
-
import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as CapRouteError, G as FrameDecoder, H as UdsLocalTransportServer, I as classifyCapRoute, J as buildUdsNativeCapProxy, K as encodeFrame, L as LocalChildClient, M as AGENT_CAP_FWD_SERVICE, N as CapRouteResolver, O as udsChildLogToWorkerEntry, P as callWithServiceDiscovery, Q as mountNativeCapService, R as LocalChildRegistry, S as ipcChildLink, T as createParentUnownedCallHandler, U as SocketChannel, V as UdsLocalTransportClient, W as localEndpointPath, Y as createBrokerDeviceManagerApi, _ as __resetCapUsageRegistryForTests, a as getWorkerDeviceRegistry, at as capBareAction, b as brokerTransportLink, c as setHubConnected, ct as deserializeTypedArrays, d as registerEventBusService, dt as CapabilityHandle, et as createAddonService, f as AddonDepsManager, ft as CapabilityUnavailableError, g as CapUsageRegistry, h as createHwAccelService, i as createUdsAddonContext, it as capActionSuffix, j as AGENT_CAP_FWD_ACTION, k as createUdsLogger, l as EVENT_TOPIC_PREFIX, lt as serializeTypedArrays, m as resolveHwAccel, mt as resolveAddonClass, n as adaptBrokerToCluster, nt as NATIVE_PROVIDER_SERVICE_INFIX, o as getOrInitReadinessRegistry, ot as capServiceName, p as createKernelHwAccel, pt as installManifestNativeDeps, q as buildNativeCapProxy, r as createAddonContext, rt as capActionName, s as getOrInitReadinessRegistryForClient, st as parseCapAction, t as installManifestPythonDeps, tt as validateProviderRegistrations, u as getBrokerEventBus, ut as DeviceRegistry, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-
|
|
22
|
+
import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as CapRouteError, G as FrameDecoder, H as UdsLocalTransportServer, I as classifyCapRoute, J as buildUdsNativeCapProxy, K as encodeFrame, L as LocalChildClient, M as AGENT_CAP_FWD_SERVICE, N as CapRouteResolver, O as udsChildLogToWorkerEntry, P as callWithServiceDiscovery, Q as mountNativeCapService, R as LocalChildRegistry, S as ipcChildLink, T as createParentUnownedCallHandler, U as SocketChannel, V as UdsLocalTransportClient, W as localEndpointPath, Y as createBrokerDeviceManagerApi, _ as __resetCapUsageRegistryForTests, a as getWorkerDeviceRegistry, at as capBareAction, b as brokerTransportLink, c as setHubConnected, ct as deserializeTypedArrays, d as registerEventBusService, dt as CapabilityHandle, et as createAddonService, f as AddonDepsManager, ft as CapabilityUnavailableError, g as CapUsageRegistry, h as createHwAccelService, i as createUdsAddonContext, it as capActionSuffix, j as AGENT_CAP_FWD_ACTION, k as createUdsLogger, l as EVENT_TOPIC_PREFIX, lt as serializeTypedArrays, m as resolveHwAccel, mt as resolveAddonClass, n as adaptBrokerToCluster, nt as NATIVE_PROVIDER_SERVICE_INFIX, o as getOrInitReadinessRegistry, ot as capServiceName, p as createKernelHwAccel, pt as installManifestNativeDeps, q as buildNativeCapProxy, r as createAddonContext, rt as capActionName, s as getOrInitReadinessRegistryForClient, st as parseCapAction, t as installManifestPythonDeps, tt as validateProviderRegistrations, u as getBrokerEventBus, ut as DeviceRegistry, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-BcrTzHH_.mjs";
|
|
23
23
|
import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
|
|
24
24
|
import { n as require_src, t as require_graceful_fs } from "./graceful-fs-BoR9GuPS.mjs";
|
|
25
25
|
import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
|
|
@@ -30,7 +30,7 @@ import * as path$40 from "node:path";
|
|
|
30
30
|
import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
31
31
|
import { DATAPLANE_SECRET_HEADER, EventCategory, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, asJsonObject, asNumber, asString, createEvent, emitDownForOwnedCaps, errMsg, lifecycleJobSchema, parseJsonObject, parseJsonUnknown, readinessKey, scopeKey } from "@camstack/types";
|
|
32
32
|
import { X509Certificate, createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
33
|
-
import { execFile,
|
|
33
|
+
import { execFile, spawn } from "node:child_process";
|
|
34
34
|
import * as util$8 from "node:util";
|
|
35
35
|
import { promisify } from "node:util";
|
|
36
36
|
import * as vm from "node:vm";
|
|
@@ -177,7 +177,7 @@ function proxyToUpstream(opts) {
|
|
|
177
177
|
}
|
|
178
178
|
//#endregion
|
|
179
179
|
//#region src/python/python-env-manager.ts
|
|
180
|
-
var execFileAsync$
|
|
180
|
+
var execFileAsync$3 = promisify(execFile);
|
|
181
181
|
var PythonEnvManager = class {
|
|
182
182
|
venvPath;
|
|
183
183
|
cachedProbe = null;
|
|
@@ -187,12 +187,12 @@ var PythonEnvManager = class {
|
|
|
187
187
|
async probe() {
|
|
188
188
|
if (this.cachedProbe) return this.cachedProbe;
|
|
189
189
|
for (const cmd of ["python3", "python"]) try {
|
|
190
|
-
const { stdout } = await execFileAsync$
|
|
190
|
+
const { stdout } = await execFileAsync$3(cmd, ["--version"]);
|
|
191
191
|
const version = stdout.trim().replace("Python ", "");
|
|
192
192
|
const major = parseInt(version.split(".")[0] ?? "0", 10);
|
|
193
193
|
const minor = parseInt(version.split(".")[1] ?? "0", 10);
|
|
194
194
|
if (major < 3 || major === 3 && minor < 10) continue;
|
|
195
|
-
const { stdout: pathOut } = await execFileAsync$
|
|
195
|
+
const { stdout: pathOut } = await execFileAsync$3(cmd, ["-c", "import sys; print(sys.executable)"]);
|
|
196
196
|
this.cachedProbe = {
|
|
197
197
|
available: true,
|
|
198
198
|
version,
|
|
@@ -208,13 +208,13 @@ var PythonEnvManager = class {
|
|
|
208
208
|
async ensure(options) {
|
|
209
209
|
const probe = await this.probe();
|
|
210
210
|
if (!probe.available || !probe.path) throw new Error("Python 3.10+ is required but not found on this system");
|
|
211
|
-
if (!fs$17.existsSync(path$40.join(this.venvPath, "bin", "python"))) await execFileAsync$
|
|
211
|
+
if (!fs$17.existsSync(path$40.join(this.venvPath, "bin", "python"))) await execFileAsync$3(probe.path, [
|
|
212
212
|
"-m",
|
|
213
213
|
"venv",
|
|
214
214
|
this.venvPath
|
|
215
215
|
]);
|
|
216
216
|
const venvPython = path$40.join(this.venvPath, "bin", "python");
|
|
217
|
-
if (options.packages.length > 0) await execFileAsync$
|
|
217
|
+
if (options.packages.length > 0) await execFileAsync$3(venvPython, [
|
|
218
218
|
"-m",
|
|
219
219
|
"pip",
|
|
220
220
|
"install",
|
|
@@ -1413,12 +1413,11 @@ var StorageManager = class {
|
|
|
1413
1413
|
return namespace ? this.createNamespacedLocation(location, namespace) : location;
|
|
1414
1414
|
}
|
|
1415
1415
|
createLegacyShim() {
|
|
1416
|
-
const self = this;
|
|
1417
1416
|
return {
|
|
1418
1417
|
async initialize() {},
|
|
1419
1418
|
async shutdown() {},
|
|
1420
|
-
getLocation(name) {
|
|
1421
|
-
return
|
|
1419
|
+
getLocation: (name) => {
|
|
1420
|
+
return this.getLocation(name);
|
|
1422
1421
|
}
|
|
1423
1422
|
};
|
|
1424
1423
|
}
|
|
@@ -1694,7 +1693,7 @@ function matchPath(pattern, path) {
|
|
|
1694
1693
|
}
|
|
1695
1694
|
//#endregion
|
|
1696
1695
|
//#region src/tls/cert-manager.ts
|
|
1697
|
-
var execFileAsync$
|
|
1696
|
+
var execFileAsync$2 = promisify(execFile);
|
|
1698
1697
|
/**
|
|
1699
1698
|
* Ensure a self-signed TLS certificate exists in the given directory.
|
|
1700
1699
|
* Generates one if missing. Returns paths to cert and key files.
|
|
@@ -1731,7 +1730,7 @@ async function ensureTlsCert(dataDir, options) {
|
|
|
1731
1730
|
for (const dns of sanDns) sanParts.push(`DNS:${dns}`);
|
|
1732
1731
|
for (const ip of sanIps) sanParts.push(`IP:${ip}`);
|
|
1733
1732
|
const sanString = sanParts.join(",");
|
|
1734
|
-
await execFileAsync$
|
|
1733
|
+
await execFileAsync$2("openssl", [
|
|
1735
1734
|
"req",
|
|
1736
1735
|
"-x509",
|
|
1737
1736
|
"-newkey",
|
|
@@ -2564,6 +2563,7 @@ var AddonEngineManager = class {
|
|
|
2564
2563
|
};
|
|
2565
2564
|
//#endregion
|
|
2566
2565
|
//#region src/kernel/fs-utils.ts
|
|
2566
|
+
var execFileAsync$1 = promisify(execFile);
|
|
2567
2567
|
/**
|
|
2568
2568
|
* Ensure a directory exists (recursive).
|
|
2569
2569
|
* Single source of truth — replaces scattered mkdirSync calls.
|
|
@@ -2572,18 +2572,20 @@ function ensureDir(dirPath) {
|
|
|
2572
2572
|
fs$17.mkdirSync(dirPath, { recursive: true });
|
|
2573
2573
|
}
|
|
2574
2574
|
/**
|
|
2575
|
-
* Copy a directory recursively.
|
|
2576
|
-
*
|
|
2575
|
+
* Copy a directory recursively — ASYNC so it never blocks the event loop.
|
|
2576
|
+
*
|
|
2577
|
+
* The hub installs addons into `/data/addons`, which on Unraid is a slow
|
|
2578
|
+
* shfs/FUSE mount. The former synchronous `fs.copyFileSync`-per-file loop
|
|
2579
|
+
* blocked the Node event loop for the whole copy of a large bundle (e.g.
|
|
2580
|
+
* `addon-pipeline`), which froze the hub's HTTP listener mid-`camstack deploy`
|
|
2581
|
+
* (accept backlog piling up, existing connections stuck in CLOSE_WAIT). Using
|
|
2582
|
+
* `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
|
|
2577
2583
|
*/
|
|
2578
|
-
function copyDirRecursive(src, dest) {
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
const destPath = path$40.join(dest, entry.name);
|
|
2584
|
-
if (entry.isDirectory()) copyDirRecursive(srcPath, destPath);
|
|
2585
|
-
else fs$17.copyFileSync(srcPath, destPath);
|
|
2586
|
-
}
|
|
2584
|
+
async function copyDirRecursive(src, dest) {
|
|
2585
|
+
await fs$17.promises.cp(src, dest, {
|
|
2586
|
+
recursive: true,
|
|
2587
|
+
force: true
|
|
2588
|
+
});
|
|
2587
2589
|
}
|
|
2588
2590
|
/**
|
|
2589
2591
|
* Strip @camstack/* dependencies and devDependencies from a package.json object.
|
|
@@ -2613,7 +2615,7 @@ function stripCamstackDeps(pkg) {
|
|
|
2613
2615
|
* Copies directories (not individual files) from source to destination.
|
|
2614
2616
|
* Skips "dist" (already handled) and glob patterns.
|
|
2615
2617
|
*/
|
|
2616
|
-
function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
|
|
2618
|
+
async function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
|
|
2617
2619
|
const rawFiles = pkgJson.files;
|
|
2618
2620
|
if (!Array.isArray(rawFiles)) return;
|
|
2619
2621
|
for (const fileEntry of rawFiles) {
|
|
@@ -2622,11 +2624,11 @@ function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
|
|
|
2622
2624
|
const srcPath = path$40.join(sourceDir, fileEntry);
|
|
2623
2625
|
if (!fs$17.existsSync(srcPath)) continue;
|
|
2624
2626
|
const destPath = path$40.join(destDir, fileEntry);
|
|
2625
|
-
const stat = fs$17.
|
|
2626
|
-
if (stat.isDirectory()) copyDirRecursive(srcPath, destPath);
|
|
2627
|
+
const stat = await fs$17.promises.stat(srcPath);
|
|
2628
|
+
if (stat.isDirectory()) await copyDirRecursive(srcPath, destPath);
|
|
2627
2629
|
else if (stat.isFile()) {
|
|
2628
2630
|
ensureDir(path$40.dirname(destPath));
|
|
2629
|
-
fs$17.
|
|
2631
|
+
await fs$17.promises.copyFile(srcPath, destPath);
|
|
2630
2632
|
}
|
|
2631
2633
|
}
|
|
2632
2634
|
}
|
|
@@ -2671,12 +2673,16 @@ function ensureLibraryBuilt(packageName, packagesDir) {
|
|
|
2671
2673
|
/**
|
|
2672
2674
|
* Install a single npm package into a target directory (package.json + dist/).
|
|
2673
2675
|
* No validation on camstack.addons -- works for any @camstack/* package.
|
|
2674
|
-
*
|
|
2676
|
+
*
|
|
2677
|
+
* ASYNC: uses `execFile`/`fs.promises` throughout so a package install on a
|
|
2678
|
+
* live node never blocks the event loop (the `npm pack` + tar extract + copy to
|
|
2679
|
+
* the slow shfs/FUSE `/data` would otherwise freeze the HTTP listener). See
|
|
2680
|
+
* `copyDirRecursive` for the wedge this prevents.
|
|
2675
2681
|
*/
|
|
2676
|
-
function
|
|
2677
|
-
const tmpDir = fs$17.
|
|
2682
|
+
async function installPackageFromNpm(packageName, targetDir) {
|
|
2683
|
+
const tmpDir = await fs$17.promises.mkdtemp(path$40.join(os$17.tmpdir(), "camstack-install-"));
|
|
2678
2684
|
try {
|
|
2679
|
-
const
|
|
2685
|
+
const { stdout } = await execFileAsync$1("npm", [
|
|
2680
2686
|
"pack",
|
|
2681
2687
|
packageName,
|
|
2682
2688
|
"--pack-destination",
|
|
@@ -2684,12 +2690,13 @@ function installPackageFromNpmSync(packageName, targetDir) {
|
|
|
2684
2690
|
], {
|
|
2685
2691
|
timeout: 12e4,
|
|
2686
2692
|
encoding: "utf-8"
|
|
2687
|
-
})
|
|
2693
|
+
});
|
|
2694
|
+
const tgzFilename = stdout.trim().split("\n").pop()?.trim();
|
|
2688
2695
|
if (!tgzFilename) throw new Error("npm pack produced no output");
|
|
2689
2696
|
const tgzPath = path$40.join(tmpDir, tgzFilename);
|
|
2690
2697
|
const extractDir = path$40.join(tmpDir, "extracted");
|
|
2691
2698
|
ensureDir(extractDir);
|
|
2692
|
-
|
|
2699
|
+
await execFileAsync$1("tar", [
|
|
2693
2700
|
"-xzf",
|
|
2694
2701
|
tgzPath,
|
|
2695
2702
|
"-C",
|
|
@@ -2697,20 +2704,20 @@ function installPackageFromNpmSync(packageName, targetDir) {
|
|
|
2697
2704
|
], { timeout: 3e4 });
|
|
2698
2705
|
const packageSubDir = path$40.join(extractDir, "package");
|
|
2699
2706
|
const srcPkgJsonDir = fs$17.existsSync(path$40.join(packageSubDir, "package.json")) ? packageSubDir : extractDir;
|
|
2700
|
-
fs$17.
|
|
2707
|
+
await fs$17.promises.rm(targetDir, {
|
|
2701
2708
|
recursive: true,
|
|
2702
2709
|
force: true
|
|
2703
2710
|
});
|
|
2704
2711
|
ensureDir(targetDir);
|
|
2705
|
-
fs$17.
|
|
2712
|
+
await fs$17.promises.copyFile(path$40.join(srcPkgJsonDir, "package.json"), path$40.join(targetDir, "package.json"));
|
|
2706
2713
|
const distSrc = path$40.join(srcPkgJsonDir, "dist");
|
|
2707
|
-
if (fs$17.existsSync(distSrc)) copyDirRecursive(distSrc, path$40.join(targetDir, "dist"));
|
|
2714
|
+
if (fs$17.existsSync(distSrc)) await copyDirRecursive(distSrc, path$40.join(targetDir, "dist"));
|
|
2708
2715
|
try {
|
|
2709
|
-
const npmPkg = asJsonObject(parseJsonUnknown(fs$17.
|
|
2710
|
-
if (npmPkg) copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
|
|
2716
|
+
const npmPkg = asJsonObject(parseJsonUnknown(await fs$17.promises.readFile(path$40.join(srcPkgJsonDir, "package.json"), "utf-8")));
|
|
2717
|
+
if (npmPkg) await copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
|
|
2711
2718
|
} catch {}
|
|
2712
2719
|
} finally {
|
|
2713
|
-
fs$17.
|
|
2720
|
+
await fs$17.promises.rm(tmpDir, {
|
|
2714
2721
|
recursive: true,
|
|
2715
2722
|
force: true
|
|
2716
2723
|
});
|
|
@@ -3160,15 +3167,15 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3160
3167
|
await this.ensureBuilt(packageName, sourceDir, pkgData);
|
|
3161
3168
|
const distDir = path$40.join(sourceDir, "dist");
|
|
3162
3169
|
if (!fs$17.existsSync(distDir)) throw new Error(`${packageName} has no dist/ after build`);
|
|
3163
|
-
fs$17.
|
|
3170
|
+
await fs$17.promises.rm(targetDir, {
|
|
3164
3171
|
recursive: true,
|
|
3165
3172
|
force: true
|
|
3166
3173
|
});
|
|
3167
3174
|
ensureDir(targetDir);
|
|
3168
|
-
fs$17.
|
|
3169
|
-
copyDirRecursive(distDir, path$40.join(targetDir, "dist"));
|
|
3170
|
-
copyExtraFileDirs(pkgData, sourceDir, targetDir);
|
|
3171
|
-
fs$17.
|
|
3175
|
+
await fs$17.promises.writeFile(path$40.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
|
|
3176
|
+
await copyDirRecursive(distDir, path$40.join(targetDir, "dist"));
|
|
3177
|
+
await copyExtraFileDirs(pkgData, sourceDir, targetDir);
|
|
3178
|
+
await fs$17.promises.writeFile(path$40.join(targetDir, ".install-source"), "local");
|
|
3172
3179
|
const localPkgVersion = asString(pkgData.version, "0.0.0");
|
|
3173
3180
|
this.manifest.upsert(packageName, {
|
|
3174
3181
|
version: localPkgVersion,
|
|
@@ -3271,6 +3278,66 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3271
3278
|
fs$17.writeFileSync(tgzPath, buf);
|
|
3272
3279
|
return tgzPath;
|
|
3273
3280
|
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Evict an existing install dir by atomic rename-aside, then best-effort
|
|
3283
|
+
* delete the aside copy.
|
|
3284
|
+
*
|
|
3285
|
+
* Why not a plain `fs.promises.rm(targetDir, {recursive})`: during a
|
|
3286
|
+
* `camstack deploy` the addon's live runner still holds its native
|
|
3287
|
+
* prebuilds (`.node`/`.so`) open. On the hub's shfs/FUSE `/data/addons`,
|
|
3288
|
+
* a recursive rm of a held subdir fails `ENOTEMPTY` mid-walk and leaves
|
|
3289
|
+
* the dir gutted. A rename only touches the dirent (metadata), so it
|
|
3290
|
+
* succeeds with the inodes still open — the old runner keeps its fds on
|
|
3291
|
+
* the moved inodes until it restarts.
|
|
3292
|
+
*
|
|
3293
|
+
* The aside copy is removed best-effort: the held native libs are only
|
|
3294
|
+
* released once the post-install `restartAddon` recycles the runner, so a
|
|
3295
|
+
* failure to delete it now must NOT fail the install. Any residue is swept
|
|
3296
|
+
* on the next deploy.
|
|
3297
|
+
*/
|
|
3298
|
+
async evictInstallDir(targetDir) {
|
|
3299
|
+
if (!fs$17.existsSync(targetDir)) return;
|
|
3300
|
+
const graveyard = path$40.join(this.addonsDir, ".evicting");
|
|
3301
|
+
ensureDir(graveyard);
|
|
3302
|
+
await this.sweepGraveyard(graveyard);
|
|
3303
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3304
|
+
const flatName = path$40.relative(this.addonsDir, targetDir).replace(/[/\\]/g, "-");
|
|
3305
|
+
const asideDir = path$40.join(graveyard, `${flatName}-${ts}`);
|
|
3306
|
+
try {
|
|
3307
|
+
fs$17.renameSync(targetDir, asideDir);
|
|
3308
|
+
} catch (renameErr) {
|
|
3309
|
+
if (renameErr.code === "EXDEV") {
|
|
3310
|
+
await fs$17.promises.rm(targetDir, {
|
|
3311
|
+
recursive: true,
|
|
3312
|
+
force: true
|
|
3313
|
+
});
|
|
3314
|
+
return;
|
|
3315
|
+
}
|
|
3316
|
+
throw renameErr;
|
|
3317
|
+
}
|
|
3318
|
+
await fs$17.promises.rm(asideDir, {
|
|
3319
|
+
recursive: true,
|
|
3320
|
+
force: true
|
|
3321
|
+
}).catch((cleanupErr) => {
|
|
3322
|
+
this.logger.debug("evictInstallDir: aside cleanup deferred (likely held open)", { meta: {
|
|
3323
|
+
asideDir,
|
|
3324
|
+
error: errMsg(cleanupErr)
|
|
3325
|
+
} });
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
/** Best-effort removal of leftover `.evicting/*` dirs from earlier deploys. */
|
|
3329
|
+
async sweepGraveyard(graveyard) {
|
|
3330
|
+
let entries;
|
|
3331
|
+
try {
|
|
3332
|
+
entries = fs$17.readdirSync(graveyard);
|
|
3333
|
+
} catch {
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
await Promise.all(entries.map((name) => fs$17.promises.rm(path$40.join(graveyard, name), {
|
|
3337
|
+
recursive: true,
|
|
3338
|
+
force: true
|
|
3339
|
+
}).catch(() => void 0)));
|
|
3340
|
+
}
|
|
3274
3341
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
3275
3342
|
async installFromTgz(tgzPath) {
|
|
3276
3343
|
const tmpDir = fs$17.mkdtempSync(path$40.join(os$17.tmpdir(), "camstack-addon-install-"));
|
|
@@ -3288,17 +3355,14 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3288
3355
|
if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
|
|
3289
3356
|
if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
|
|
3290
3357
|
const targetDir = path$40.join(this.addonsDir, pkgView.name);
|
|
3291
|
-
|
|
3292
|
-
recursive: true,
|
|
3293
|
-
force: true
|
|
3294
|
-
});
|
|
3358
|
+
await this.evictInstallDir(targetDir);
|
|
3295
3359
|
ensureDir(targetDir);
|
|
3296
3360
|
const sourceDir = path$40.dirname(pkgJsonPath);
|
|
3297
3361
|
const strippedManifest = stripCamstackDeps(pkgView.raw);
|
|
3298
|
-
fs$17.
|
|
3362
|
+
await fs$17.promises.writeFile(path$40.join(targetDir, "package.json"), JSON.stringify(strippedManifest, null, 2));
|
|
3299
3363
|
const sourceDist = path$40.join(sourceDir, "dist");
|
|
3300
|
-
if (fs$17.existsSync(sourceDist)) copyDirRecursive(sourceDist, path$40.join(targetDir, "dist"));
|
|
3301
|
-
copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
|
|
3364
|
+
if (fs$17.existsSync(sourceDist)) await copyDirRecursive(sourceDist, path$40.join(targetDir, "dist"));
|
|
3365
|
+
await copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
|
|
3302
3366
|
const strippedRuntimeDeps = strippedManifest["dependencies"];
|
|
3303
3367
|
if (strippedRuntimeDeps != null && typeof strippedRuntimeDeps === "object" && Object.keys(strippedRuntimeDeps).length > 0) {
|
|
3304
3368
|
this.logger.info(`${pkgView.name} — installing runtime dependencies`, { meta: { targetDir } });
|
|
@@ -3322,7 +3386,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3322
3386
|
try {
|
|
3323
3387
|
await installManifestNativeDeps(targetDir, pkgView.raw, this.logger, this.registry);
|
|
3324
3388
|
} catch (nativeErr) {
|
|
3325
|
-
fs$17.
|
|
3389
|
+
await fs$17.promises.rm(targetDir, {
|
|
3326
3390
|
recursive: true,
|
|
3327
3391
|
force: true
|
|
3328
3392
|
});
|
|
@@ -3337,7 +3401,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3337
3401
|
version: pkgView.version
|
|
3338
3402
|
};
|
|
3339
3403
|
} finally {
|
|
3340
|
-
fs$17.
|
|
3404
|
+
await fs$17.promises.rm(tmpDir, {
|
|
3341
3405
|
recursive: true,
|
|
3342
3406
|
force: true
|
|
3343
3407
|
});
|
|
@@ -4105,7 +4169,7 @@ var CapabilityRegistry = class CapabilityRegistry {
|
|
|
4105
4169
|
const bare = this.bareAddonId(addonId);
|
|
4106
4170
|
const nodeMap = this.singletonNodeOverrides.get(capabilityName);
|
|
4107
4171
|
if (nodeMap) {
|
|
4108
|
-
for (const [nodeId, ov] of
|
|
4172
|
+
for (const [nodeId, ov] of Array.from(nodeMap)) if (ov === bare) nodeMap.delete(nodeId);
|
|
4109
4173
|
if (nodeMap.size === 0) this.singletonNodeOverrides.delete(capabilityName);
|
|
4110
4174
|
}
|
|
4111
4175
|
this.logger.info("Provider unregistered from capability", {
|
|
@@ -7382,20 +7446,20 @@ var ConfigManager = class ConfigManager {
|
|
|
7382
7446
|
setSettingsStore(store) {
|
|
7383
7447
|
this.settingsStore = store;
|
|
7384
7448
|
}
|
|
7385
|
-
get(
|
|
7386
|
-
return this.resolveConfigValue(
|
|
7449
|
+
get(configPath) {
|
|
7450
|
+
return this.resolveConfigValue(configPath);
|
|
7387
7451
|
}
|
|
7388
|
-
resolveConfigValue(
|
|
7389
|
-
const bootstrapValue = this.getFromBootstrap(
|
|
7452
|
+
resolveConfigValue(configPath) {
|
|
7453
|
+
const bootstrapValue = this.getFromBootstrap(configPath);
|
|
7390
7454
|
if (bootstrapValue !== void 0) return bootstrapValue;
|
|
7391
7455
|
if (this.settingsStore !== null) {
|
|
7392
|
-
const storeValue = this.settingsStore.getSystem(
|
|
7456
|
+
const storeValue = this.settingsStore.getSystem(configPath);
|
|
7393
7457
|
if (storeValue !== void 0) return storeValue;
|
|
7394
|
-
const nested = this.getNestedFromSystemSettings(
|
|
7458
|
+
const nested = this.getNestedFromSystemSettings(configPath);
|
|
7395
7459
|
if (nested !== null) return nested;
|
|
7396
7460
|
}
|
|
7397
|
-
if (
|
|
7398
|
-
return this.getFromRuntimeDefaults(
|
|
7461
|
+
if (configPath in RUNTIME_DEFAULTS) return RUNTIME_DEFAULTS[configPath];
|
|
7462
|
+
return this.getFromRuntimeDefaults(configPath) ?? void 0;
|
|
7399
7463
|
}
|
|
7400
7464
|
/**
|
|
7401
7465
|
* Write a value to the settings-store.
|
|
@@ -7611,8 +7675,8 @@ var ConfigManager = class ConfigManager {
|
|
|
7611
7675
|
};
|
|
7612
7676
|
this.saveRuntimeState();
|
|
7613
7677
|
}
|
|
7614
|
-
getBootstrap(
|
|
7615
|
-
return this.getFromBootstrap(
|
|
7678
|
+
getBootstrap(configPath) {
|
|
7679
|
+
return this.getFromBootstrap(configPath);
|
|
7616
7680
|
}
|
|
7617
7681
|
/** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
|
|
7618
7682
|
get features() {
|
|
@@ -7702,8 +7766,8 @@ var ConfigManager = class ConfigManager {
|
|
|
7702
7766
|
* Deep-set a value in a nested plain object using a dot-notation path.
|
|
7703
7767
|
* Returns a new object (immutable).
|
|
7704
7768
|
*/
|
|
7705
|
-
setNested(obj,
|
|
7706
|
-
const [head, ...rest] =
|
|
7769
|
+
setNested(obj, configPath, value) {
|
|
7770
|
+
const [head, ...rest] = configPath.split(".");
|
|
7707
7771
|
if (!head) return obj;
|
|
7708
7772
|
if (rest.length === 0) return {
|
|
7709
7773
|
...obj,
|
|
@@ -7743,8 +7807,8 @@ var ConfigManager = class ConfigManager {
|
|
|
7743
7807
|
warnDefaultCredentials() {
|
|
7744
7808
|
if (this.bootstrapConfig.auth.adminPassword === "changeme") console.warn("[ConfigManager] Warning: Using default admin password \"changeme\". Set auth.adminPassword in your config.yaml or the CAMSTACK_ADMIN_PASS env var.");
|
|
7745
7809
|
}
|
|
7746
|
-
getFromBootstrap(
|
|
7747
|
-
const keys =
|
|
7810
|
+
getFromBootstrap(configPath) {
|
|
7811
|
+
const keys = configPath.split(".");
|
|
7748
7812
|
let current = this.bootstrapConfig;
|
|
7749
7813
|
for (const key of keys) {
|
|
7750
7814
|
if (!isRecord(current)) return void 0;
|
|
@@ -7752,8 +7816,8 @@ var ConfigManager = class ConfigManager {
|
|
|
7752
7816
|
}
|
|
7753
7817
|
return current;
|
|
7754
7818
|
}
|
|
7755
|
-
getFromRuntimeDefaults(
|
|
7756
|
-
const prefix =
|
|
7819
|
+
getFromRuntimeDefaults(configPath) {
|
|
7820
|
+
const prefix = configPath + ".";
|
|
7757
7821
|
const result = {};
|
|
7758
7822
|
let found = false;
|
|
7759
7823
|
for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) if (key.startsWith(prefix)) {
|
|
@@ -7768,10 +7832,10 @@ var ConfigManager = class ConfigManager {
|
|
|
7768
7832
|
* e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.
|
|
7769
7833
|
* Returns an object keyed by the sub-key, or undefined if nothing is found.
|
|
7770
7834
|
*/
|
|
7771
|
-
getNestedFromSystemSettings(
|
|
7835
|
+
getNestedFromSystemSettings(configPath) {
|
|
7772
7836
|
if (this.settingsStore === null) return null;
|
|
7773
7837
|
const all = this.settingsStore.getAllSystem();
|
|
7774
|
-
const prefix =
|
|
7838
|
+
const prefix = configPath + ".";
|
|
7775
7839
|
const result = {};
|
|
7776
7840
|
let found = false;
|
|
7777
7841
|
for (const [key, value] of Object.entries(all)) if (key.startsWith(prefix)) {
|
|
@@ -89891,6 +89955,15 @@ function normalizeHubUrl(raw, defaultPort) {
|
|
|
89891
89955
|
return `${/:\d+$/.test(hostPort) ? hostPort : `${hostPort}:${defaultPort}`}/${nodeId}`;
|
|
89892
89956
|
}
|
|
89893
89957
|
//#endregion
|
|
89958
|
+
//#region src/kernel/moleculer/addon-deploy-source.ts
|
|
89959
|
+
function isAddonDeploySource(v) {
|
|
89960
|
+
if (!v || typeof v !== "object") return false;
|
|
89961
|
+
const o = v;
|
|
89962
|
+
if (o["kind"] === "npm") return typeof o["version"] === "string";
|
|
89963
|
+
if (o["kind"] === "hub-http") return typeof o["url"] === "string" && typeof o["token"] === "string" && typeof o["sha256"] === "string" && typeof o["bytes"] === "number";
|
|
89964
|
+
return false;
|
|
89965
|
+
}
|
|
89966
|
+
//#endregion
|
|
89894
89967
|
//#region src/kernel/moleculer/core-cap-service.ts
|
|
89895
89968
|
/**
|
|
89896
89969
|
* Default Moleculer service name for the hub core-capability bridge.
|
|
@@ -90932,7 +91005,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
90932
91005
|
respawned.restartCount = prevRestartCount + 1;
|
|
90933
91006
|
});
|
|
90934
91007
|
restarted.push(name);
|
|
90935
|
-
} catch
|
|
91008
|
+
} catch {
|
|
90936
91009
|
failed.push(name);
|
|
90937
91010
|
}
|
|
90938
91011
|
return {
|
|
@@ -91327,8 +91400,8 @@ var LifecycleJobEngine = class {
|
|
|
91327
91400
|
} finally {
|
|
91328
91401
|
clearTimeout(timer);
|
|
91329
91402
|
}
|
|
91330
|
-
if (packages.length === 0) throw new Error("stageFramework returned no packages");
|
|
91331
91403
|
const [firstPackage] = packages;
|
|
91404
|
+
if (!firstPackage) throw new Error("stageFramework returned no packages");
|
|
91332
91405
|
this.advance(job.jobId, task, "staged", { stagedPath: firstPackage.stagedPath });
|
|
91333
91406
|
requestFrameworkSwap({
|
|
91334
91407
|
jobId: job.jobId,
|
|
@@ -91351,14 +91424,15 @@ var LifecycleJobEngine = class {
|
|
|
91351
91424
|
*/
|
|
91352
91425
|
async fetchAddonsBounded(job, addonTasks) {
|
|
91353
91426
|
const limit = Math.max(1, this.deps.fetchConcurrency ?? DEFAULT_FETCH_CONCURRENCY);
|
|
91354
|
-
const outcomes =
|
|
91427
|
+
const outcomes = Array.from({ length: addonTasks.length });
|
|
91355
91428
|
let nextIndex = 0;
|
|
91356
91429
|
const worker = async () => {
|
|
91357
91430
|
for (;;) {
|
|
91358
91431
|
const index = nextIndex;
|
|
91359
91432
|
nextIndex += 1;
|
|
91360
|
-
|
|
91361
|
-
|
|
91433
|
+
const addonTask = addonTasks[index];
|
|
91434
|
+
if (addonTask === void 0) return;
|
|
91435
|
+
outcomes[index] = await this.fetchAddonTask(job, addonTask);
|
|
91362
91436
|
}
|
|
91363
91437
|
};
|
|
91364
91438
|
const workerCount = Math.min(limit, addonTasks.length);
|
|
@@ -91484,4 +91558,4 @@ async function stageFrameworkLockstep(input) {
|
|
|
91484
91558
|
return results;
|
|
91485
91559
|
}
|
|
91486
91560
|
//#endregion
|
|
91487
|
-
export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps,
|
|
91561
|
+
export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isClusterSecretMismatchError, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, stageFrameworkLockstep, stripCamstackDeps, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
|
|
@@ -167,6 +167,26 @@ export declare class AddonInstaller {
|
|
|
167
167
|
* the legacy fallback path remains the right answer for those.
|
|
168
168
|
*/
|
|
169
169
|
private downloadNpmTarball;
|
|
170
|
+
/**
|
|
171
|
+
* Evict an existing install dir by atomic rename-aside, then best-effort
|
|
172
|
+
* delete the aside copy.
|
|
173
|
+
*
|
|
174
|
+
* Why not a plain `fs.promises.rm(targetDir, {recursive})`: during a
|
|
175
|
+
* `camstack deploy` the addon's live runner still holds its native
|
|
176
|
+
* prebuilds (`.node`/`.so`) open. On the hub's shfs/FUSE `/data/addons`,
|
|
177
|
+
* a recursive rm of a held subdir fails `ENOTEMPTY` mid-walk and leaves
|
|
178
|
+
* the dir gutted. A rename only touches the dirent (metadata), so it
|
|
179
|
+
* succeeds with the inodes still open — the old runner keeps its fds on
|
|
180
|
+
* the moved inodes until it restarts.
|
|
181
|
+
*
|
|
182
|
+
* The aside copy is removed best-effort: the held native libs are only
|
|
183
|
+
* released once the post-install `restartAddon` recycles the runner, so a
|
|
184
|
+
* failure to delete it now must NOT fail the install. Any residue is swept
|
|
185
|
+
* on the next deploy.
|
|
186
|
+
*/
|
|
187
|
+
private evictInstallDir;
|
|
188
|
+
/** Best-effort removal of leftover `.evicting/*` dirs from earlier deploys. */
|
|
189
|
+
private sweepGraveyard;
|
|
170
190
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
171
191
|
installFromTgz(tgzPath: string): Promise<{
|
|
172
192
|
name: string;
|
|
@@ -79,8 +79,8 @@ export declare class ConfigManager {
|
|
|
79
79
|
* as a convenience alias for legacy call sites; it performs NO runtime
|
|
80
80
|
* check and is the one documented type-level bridge in this function.
|
|
81
81
|
*/
|
|
82
|
-
get(
|
|
83
|
-
get<T>(
|
|
82
|
+
get(configPath: string): unknown;
|
|
83
|
+
get<T>(configPath: string): T | undefined;
|
|
84
84
|
private resolveConfigValue;
|
|
85
85
|
/**
|
|
86
86
|
* Write a value to the settings-store.
|
|
@@ -171,8 +171,8 @@ export declare class ConfigManager {
|
|
|
171
171
|
/** Get a value from the parsed bootstrap config.
|
|
172
172
|
* Generic overload is a documented type-level bridge — callers are responsible
|
|
173
173
|
* for passing a T that matches the config.yaml shape. */
|
|
174
|
-
getBootstrap(
|
|
175
|
-
getBootstrap<T>(
|
|
174
|
+
getBootstrap(configPath: string): unknown;
|
|
175
|
+
getBootstrap<T>(configPath: string): T | undefined;
|
|
176
176
|
/** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
|
|
177
177
|
get features(): FeatureManifest;
|
|
178
178
|
/**
|
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function ensureDir(dirPath: string): void;
|
|
6
6
|
/**
|
|
7
|
-
* Copy a directory recursively.
|
|
8
|
-
*
|
|
7
|
+
* Copy a directory recursively — ASYNC so it never blocks the event loop.
|
|
8
|
+
*
|
|
9
|
+
* The hub installs addons into `/data/addons`, which on Unraid is a slow
|
|
10
|
+
* shfs/FUSE mount. The former synchronous `fs.copyFileSync`-per-file loop
|
|
11
|
+
* blocked the Node event loop for the whole copy of a large bundle (e.g.
|
|
12
|
+
* `addon-pipeline`), which froze the hub's HTTP listener mid-`camstack deploy`
|
|
13
|
+
* (accept backlog piling up, existing connections stuck in CLOSE_WAIT). Using
|
|
14
|
+
* `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
|
|
9
15
|
*/
|
|
10
|
-
export declare function copyDirRecursive(src: string, dest: string): void
|
|
16
|
+
export declare function copyDirRecursive(src: string, dest: string): Promise<void>;
|
|
11
17
|
/**
|
|
12
18
|
* Strip @camstack/* dependencies and devDependencies from a package.json object.
|
|
13
19
|
* Used when installing addons into the addons directory — @camstack packages
|
|
@@ -21,7 +27,7 @@ export declare function stripCamstackDeps(pkg: Record<string, unknown>): Record<
|
|
|
21
27
|
* Copies directories (not individual files) from source to destination.
|
|
22
28
|
* Skips "dist" (already handled) and glob patterns.
|
|
23
29
|
*/
|
|
24
|
-
export declare function copyExtraFileDirs(pkgJson: Record<string, unknown>, sourceDir: string, destDir: string): void
|
|
30
|
+
export declare function copyExtraFileDirs(pkgJson: Record<string, unknown>, sourceDir: string, destDir: string): Promise<void>;
|
|
25
31
|
/**
|
|
26
32
|
* Check if any file in src/ is newer than dist/.
|
|
27
33
|
* Returns true if rebuild is needed.
|
|
@@ -36,6 +42,10 @@ export declare function ensureLibraryBuilt(packageName: string, packagesDir: str
|
|
|
36
42
|
/**
|
|
37
43
|
* Install a single npm package into a target directory (package.json + dist/).
|
|
38
44
|
* No validation on camstack.addons -- works for any @camstack/* package.
|
|
39
|
-
*
|
|
45
|
+
*
|
|
46
|
+
* ASYNC: uses `execFile`/`fs.promises` throughout so a package install on a
|
|
47
|
+
* live node never blocks the event loop (the `npm pack` + tar extract + copy to
|
|
48
|
+
* the slow shfs/FUSE `/data` would otherwise freeze the HTTP listener). See
|
|
49
|
+
* `copyDirRecursive` for the wedge this prevents.
|
|
40
50
|
*/
|
|
41
|
-
export declare function
|
|
51
|
+
export declare function installPackageFromNpm(packageName: string, targetDir: string): Promise<void>;
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type { AddonInstallerConfig, InstallSource, InstalledPackage } from './ad
|
|
|
5
5
|
export { AddonInstaller } from './addon-installer.js';
|
|
6
6
|
export type { AddonInstallSource, AddonManifestEntry, AddonManifestFile } from './addon-manifest.js';
|
|
7
7
|
export { AddonManifest } from './addon-manifest.js';
|
|
8
|
-
export { copyDirRecursive, ensureDir, stripCamstackDeps, copyExtraFileDirs, ensureLibraryBuilt,
|
|
8
|
+
export { copyDirRecursive, ensureDir, stripCamstackDeps, copyExtraFileDirs, ensureLibraryBuilt, installPackageFromNpm, isSourceNewer, } from './fs-utils.js';
|
|
9
9
|
export { detectWorkspacePackagesDir } from './workspace-detect.js';
|
|
10
10
|
export { RESTART_MARKER_FILE, clearPendingRestart, getRestartMarkerPath, readPendingRestart, scheduleSelfRestart, writePendingRestart, } from './restart-coordinator.js';
|
|
11
11
|
export type { PendingRestartMarker, RestartKind, ScheduleSelfRestartOptions, } from './restart-coordinator.js';
|
|
@@ -27,6 +27,7 @@ export { buildStorageLocationRegistry } from './storage-location-registry.js';
|
|
|
27
27
|
export type { StorageLocationRegistry } from './storage-location-registry.js';
|
|
28
28
|
export { createBroker, deriveAgentListenPort } from './moleculer/broker-factory.js';
|
|
29
29
|
export type { BrokerConfig } from './moleculer/broker-factory.js';
|
|
30
|
+
export { type AddonDeploySource, isAddonDeploySource } from './moleculer/addon-deploy-source.js';
|
|
30
31
|
export { createAddonService, validateProviderRegistrations, } from './moleculer/addon-service-factory.js';
|
|
31
32
|
export { createCoreCapService, CORE_CAP_SERVICE_NAME } from './moleculer/core-cap-service.js';
|
|
32
33
|
export type { CoreCapAction, CoreCapServiceOptions } from './moleculer/core-cap-service.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an agent should fetch an addon's bytes from for a `$agent.deploy`.
|
|
3
|
+
* `npm`: the agent runs its own AddonInstaller pull. `hub-http`: the agent
|
|
4
|
+
* streams the staged tgz from the hub's one-time-token bundle route.
|
|
5
|
+
*/
|
|
6
|
+
export type AddonDeploySource = {
|
|
7
|
+
readonly kind: 'npm';
|
|
8
|
+
readonly version: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'hub-http';
|
|
11
|
+
readonly url: string;
|
|
12
|
+
readonly token: string;
|
|
13
|
+
readonly sha256: string;
|
|
14
|
+
readonly bytes: number;
|
|
15
|
+
};
|
|
16
|
+
export declare function isAddonDeploySource(v: unknown): v is AddonDeploySource;
|