@kybernesis/create 0.14.9 → 0.14.10
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/docker-prune-cron.d.ts +8 -0
- package/dist/docker-prune-cron.js +38 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +17 -5
- package/dist/upgrade.js +31 -10
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function registeredDockerPruneAppDirs(installedContent: string | null): string[];
|
|
2
|
+
export interface DockerPruneCronArtifact {
|
|
3
|
+
content: Buffer;
|
|
4
|
+
appDirs: string[];
|
|
5
|
+
manualCommand: string;
|
|
6
|
+
}
|
|
7
|
+
/** Render the package script with a managed, mergeable app-directory header. */
|
|
8
|
+
export declare function dockerPruneCronArtifact(packageSource: string, currentAppDir: string, installedContent: string | null, targetPath: string): DockerPruneCronArtifact;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const MANAGED_PREFIX = "# kyb-managed-eve-app-dirs: ";
|
|
2
|
+
function shellSingleQuote(value) {
|
|
3
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
4
|
+
}
|
|
5
|
+
export function registeredDockerPruneAppDirs(installedContent) {
|
|
6
|
+
if (!installedContent)
|
|
7
|
+
return [];
|
|
8
|
+
const line = installedContent.split("\n").find((candidate) => candidate.startsWith(MANAGED_PREFIX));
|
|
9
|
+
if (!line)
|
|
10
|
+
return [];
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(line.slice(MANAGED_PREFIX.length));
|
|
13
|
+
return Array.isArray(parsed) && parsed.every((value) => typeof value === "string") ? parsed : [];
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Render the package script with a managed, mergeable app-directory header. */
|
|
20
|
+
export function dockerPruneCronArtifact(packageSource, currentAppDir, installedContent, targetPath) {
|
|
21
|
+
const appDirs = [...new Set([...registeredDockerPruneAppDirs(installedContent), currentAppDir])];
|
|
22
|
+
const newline = packageSource.indexOf("\n");
|
|
23
|
+
const shebang = newline === -1 ? packageSource : packageSource.slice(0, newline);
|
|
24
|
+
const body = newline === -1 ? "" : packageSource.slice(newline + 1);
|
|
25
|
+
const joined = appDirs.join(":");
|
|
26
|
+
const content = Buffer.from(`${shebang}\n` +
|
|
27
|
+
`${MANAGED_PREFIX}${JSON.stringify(appDirs)}\n` +
|
|
28
|
+
`if [ -z "\${EVE_APP_DIRS:-}" ]; then EVE_APP_DIRS=${shellSingleQuote(joined)}; fi\n` +
|
|
29
|
+
`export EVE_APP_DIRS\n` +
|
|
30
|
+
body);
|
|
31
|
+
const encoded = content.toString("base64");
|
|
32
|
+
return {
|
|
33
|
+
content,
|
|
34
|
+
appDirs,
|
|
35
|
+
manualCommand: `printf %s ${shellSingleQuote(encoded)} | base64 -d | ` +
|
|
36
|
+
`sudo install -m 0755 /dev/stdin ${shellSingleQuote(targetPath)}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface DockerTemplateInspection {
|
|
|
22
22
|
export declare function dockerTemplateDoctorChecks(result: DockerTemplateInspection): Check[];
|
|
23
23
|
export declare function modelReachDoctorCheck(authoredSource: string | null, compiledRouting: CompiledModelRouting): Check;
|
|
24
24
|
export declare function agentInputLimitDoctorCheck(limit: AgentInputLimit): Check;
|
|
25
|
+
export declare function diskUsageDoctorCheck(percent: number, dockerSummary: string | null): Check;
|
|
25
26
|
/**
|
|
26
27
|
* The inspector lives in @kybernesis/exe, next to the runtime that owns the
|
|
27
28
|
* templates; create already resolves that package's scripts out of
|
package/dist/doctor.js
CHANGED
|
@@ -84,6 +84,15 @@ export function agentInputLimitDoctorCheck(limit) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
export function diskUsageDoctorCheck(percent, dockerSummary) {
|
|
88
|
+
if (percent < 85)
|
|
89
|
+
return { verdict: "pass", label: `disk ${percent}% used` };
|
|
90
|
+
const detail = `Docker: ${dockerSummary?.trim() || "docker system df unavailable"}. ` +
|
|
91
|
+
`Run: sudo /etc/cron.daily/kyb-docker-prune`;
|
|
92
|
+
if (percent < 95)
|
|
93
|
+
return { verdict: "warn", label: `disk ${percent}% used`, detail };
|
|
94
|
+
return { verdict: "fail", label: `disk ${percent}% used`, detail };
|
|
95
|
+
}
|
|
87
96
|
/**
|
|
88
97
|
* The inspector lives in @kybernesis/exe, next to the runtime that owns the
|
|
89
98
|
* templates; create already resolves that package's scripts out of
|
|
@@ -406,14 +415,17 @@ export async function doctor() {
|
|
|
406
415
|
const job = capture("sh", ["-c", "test -x /etc/cron.daily/kyb-docker-prune && echo yes"])?.trim();
|
|
407
416
|
const percent = Number(capture("sh", ["-c", "df / | awk 'NR==2{print $5}' | tr -d '%'"])?.trim() ?? 0);
|
|
408
417
|
if (job !== "yes") {
|
|
409
|
-
add("warn", "no daily docker reclaim on this host", "sandbox templates, build cache, and generic stopped containers accumulate;
|
|
410
|
-
}
|
|
411
|
-
else if (percent >= 80) {
|
|
412
|
-
add("fail", `disk ${percent}% full despite the reclaim job`, "run it now: sudo /etc/cron.daily/kyb-docker-prune, and check what else is on this host");
|
|
418
|
+
add("warn", "no daily docker reclaim on this host", "sandbox templates, build cache, and generic stopped containers accumulate; `kyb upgrade` installs /etc/cron.daily/kyb-docker-prune");
|
|
413
419
|
}
|
|
414
420
|
else {
|
|
415
|
-
add("pass",
|
|
421
|
+
add("pass", "daily docker reclaim installed");
|
|
416
422
|
}
|
|
423
|
+
const dockerSummary = capture("sh", [
|
|
424
|
+
"-c",
|
|
425
|
+
"docker system df 2>/dev/null | head -3 | paste -sd ';' -",
|
|
426
|
+
]);
|
|
427
|
+
const diskCheck = diskUsageDoctorCheck(percent, dockerSummary);
|
|
428
|
+
add(diskCheck.verdict, diskCheck.label, diskCheck.detail);
|
|
417
429
|
}
|
|
418
430
|
/**
|
|
419
431
|
* Instructions that name a tool the agent does not have.
|
package/dist/upgrade.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { upsertEnv } from "./envfile.js";
|
|
6
7
|
import { reconcileHostArtifact } from "./host-artifacts.js";
|
|
8
|
+
import { dockerPruneCronArtifact } from "./docker-prune-cron.js";
|
|
7
9
|
import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
|
|
8
10
|
import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
|
|
9
11
|
import { repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
|
|
@@ -246,7 +248,7 @@ function repairHostArtifacts(cwd, deps) {
|
|
|
246
248
|
return;
|
|
247
249
|
const scripts = join(cwd, "node_modules/@kybernesis/exe/scripts");
|
|
248
250
|
const pruneSource = join(scripts, "docker-prune.sh");
|
|
249
|
-
const pruneTarget = "/etc/cron.daily/kyb-docker-prune";
|
|
251
|
+
const pruneTarget = process.env.KYB_DOCKER_PRUNE_TARGET ?? "/etc/cron.daily/kyb-docker-prune";
|
|
250
252
|
const hasDocker = capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() === "yes";
|
|
251
253
|
// The host start script is package-owned too. It carried a 45-second
|
|
252
254
|
// verdict clock that called a cold sandbox template build a failure, and no
|
|
@@ -276,18 +278,37 @@ function repairHostArtifacts(cwd, deps) {
|
|
|
276
278
|
});
|
|
277
279
|
}
|
|
278
280
|
if (existsSync(pruneSource)) {
|
|
281
|
+
let installedPrune = null;
|
|
282
|
+
try {
|
|
283
|
+
if (existsSync(pruneTarget))
|
|
284
|
+
installedPrune = readFileSync(pruneTarget, "utf8");
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// reconcileHostArtifact reports the unreadable target and refuses to replace it.
|
|
288
|
+
}
|
|
289
|
+
const pruneArtifact = dockerPruneCronArtifact(readFileSync(pruneSource, "utf8"), cwd, installedPrune, pruneTarget);
|
|
279
290
|
const pruneResult = reconcileHostArtifact({
|
|
280
291
|
targetPath: pruneTarget,
|
|
281
|
-
desiredContent:
|
|
292
|
+
desiredContent: pruneArtifact.content,
|
|
282
293
|
expectedMode: 0o755,
|
|
283
294
|
installIfMissing: hasDocker,
|
|
284
|
-
owner: "@kybernesis/exe/scripts/docker-prune.sh",
|
|
285
|
-
update: () =>
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
295
|
+
owner: "@kybernesis/exe/scripts/docker-prune.sh with registered app directories",
|
|
296
|
+
update: () => {
|
|
297
|
+
const stageDir = mkdtempSync(join(tmpdir(), "kyb-docker-prune-"));
|
|
298
|
+
const staged = join(stageDir, "docker-prune.sh");
|
|
299
|
+
try {
|
|
300
|
+
writeFileSync(staged, pruneArtifact.content, { mode: 0o755 });
|
|
301
|
+
return run("sudo", ["-n", "install", "-m", "0755", staged, pruneTarget], {
|
|
302
|
+
cwd,
|
|
303
|
+
allowFail: true,
|
|
304
|
+
quiet: true,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
rmSync(stageDir, { recursive: true, force: true });
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
manualCommand: pruneArtifact.manualCommand,
|
|
291
312
|
});
|
|
292
313
|
if ((pruneResult === "current" || pruneResult === "updated") && existsSync("/etc/cron.weekly/docker-prune")) {
|
|
293
314
|
const removed = run("sudo", ["-n", "rm", "-f", "/etc/cron.weekly/docker-prune"], {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.10",
|
|
4
4
|
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|