@rallycry/conveyor-k3 0.1.13 → 0.1.15
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/cli.js +968 -256
- package/dist/index.d.ts +37 -30
- package/dist/index.js +839 -216
- package/dist/vm-qemu.js +6472 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4162,7 +4162,7 @@ var coerce = {
|
|
|
4162
4162
|
};
|
|
4163
4163
|
var NEVER = INVALID;
|
|
4164
4164
|
|
|
4165
|
-
// ../shared/dist/chunk-
|
|
4165
|
+
// ../shared/dist/chunk-SFSQ7NKP.js
|
|
4166
4166
|
var DEFAULT_SONNET_MODEL = "claude-sonnet-5-5";
|
|
4167
4167
|
var DEFAULT_OPUS_MODEL = "claude-opus-5-5";
|
|
4168
4168
|
var DEFAULT_HAIKU_MODEL = "claude-haiku-4-5-20251001";
|
|
@@ -4356,6 +4356,15 @@ var ALLOWED_FILE_ACCEPT = [
|
|
|
4356
4356
|
var EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
|
|
4357
4357
|
var EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
|
|
4358
4358
|
var IDLE_HEARTBEAT_MS = 90 * 1e3;
|
|
4359
|
+
var siteAssetExtension = external_exports.string().regex(/^[a-z0-9]{1,10}$/);
|
|
4360
|
+
var RequestSiteAssetUploadRequestSchema = external_exports.object({
|
|
4361
|
+
projectId: external_exports.string(),
|
|
4362
|
+
siteId: external_exports.string().min(1).max(100),
|
|
4363
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
4364
|
+
ext: siteAssetExtension,
|
|
4365
|
+
sizeBytes: external_exports.number().int().positive()
|
|
4366
|
+
});
|
|
4367
|
+
var ListSiteAssetTargetsRequestSchema = external_exports.object({ projectId: external_exports.string() });
|
|
4359
4368
|
var TurnEndToolCallSchema = external_exports.object({
|
|
4360
4369
|
tool: external_exports.string(),
|
|
4361
4370
|
input: external_exports.string().optional(),
|
|
@@ -4647,7 +4656,9 @@ var GetTaskFilesRequestSchema = external_exports.object({
|
|
|
4647
4656
|
var GetTaskFileRequestSchema = external_exports.object({
|
|
4648
4657
|
sessionId: external_exports.string(),
|
|
4649
4658
|
fileId: external_exports.string(),
|
|
4650
|
-
taskId: external_exports.string().optional()
|
|
4659
|
+
taskId: external_exports.string().optional(),
|
|
4660
|
+
/** Ask for a short-lived `agentDownloadUrl` when the bytes are not inlined. */
|
|
4661
|
+
agentDownload: external_exports.boolean().optional()
|
|
4651
4662
|
});
|
|
4652
4663
|
var DeleteTaskFileRequestSchema = external_exports.object({
|
|
4653
4664
|
sessionId: external_exports.string(),
|
|
@@ -4787,10 +4798,6 @@ var ReportDiscoveredPortsRequestSchema = external_exports.object({
|
|
|
4787
4798
|
sessionId: external_exports.string(),
|
|
4788
4799
|
ports: external_exports.array(DiscoveredPortSchema).max(64)
|
|
4789
4800
|
});
|
|
4790
|
-
var ReportBootMilestoneRequestSchema = external_exports.object({
|
|
4791
|
-
sessionId: external_exports.string(),
|
|
4792
|
-
key: external_exports.string().max(64)
|
|
4793
|
-
});
|
|
4794
4801
|
var CreateSubtaskRequestSchema = external_exports.object({
|
|
4795
4802
|
sessionId: external_exports.string(),
|
|
4796
4803
|
title: external_exports.string().min(1),
|
|
@@ -6266,6 +6273,8 @@ var ANTHROPIC_CATALOG = [
|
|
|
6266
6273
|
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
6267
6274
|
anthropicEntry(FABLE_MODEL, "Fable 5.1", 10, 50)
|
|
6268
6275
|
];
|
|
6276
|
+
var PROJECT_ARCHIVE_RETENTION_DAYS = 30;
|
|
6277
|
+
var RETENTION_MS = PROJECT_ARCHIVE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
6269
6278
|
var CLAUDESPACE_WORKLOAD_LABEL = "rc-workload";
|
|
6270
6279
|
var CLAUDESPACE_WORKLOAD_VALUE = "claudespace";
|
|
6271
6280
|
var CONVEYOR_POD_SELECTOR = `${CLAUDESPACE_WORKLOAD_LABEL}=${CLAUDESPACE_WORKLOAD_VALUE}`;
|
|
@@ -6292,6 +6301,8 @@ var PERSONAL_LOCAL_REGISTRY_NAME = "conveyor-registry";
|
|
|
6292
6301
|
var PERSONAL_LOCAL_REGISTRY_PORT = 5e3;
|
|
6293
6302
|
var PERSONAL_LOCAL_REGISTRY_HOST = `${PERSONAL_LOCAL_REGISTRY_NAME}.${PERSONAL_LOCAL_REGISTRY_NAMESPACE}.svc.cluster.local:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6294
6303
|
var PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT = `http://127.0.0.1:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6304
|
+
var PERSONAL_LOCAL_REGISTRY_PUSH_SECRET = "conveyor-registry-push";
|
|
6305
|
+
var PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY = "config.json";
|
|
6295
6306
|
var BAKE_COMPONENT_LABEL = "claudespace.rallycry.com/component";
|
|
6296
6307
|
var BAKE_COMPONENT_PREBAKE = "prebake";
|
|
6297
6308
|
var BAKE_COMPONENT_STATE_BAKE = "state-bake";
|
|
@@ -6598,6 +6609,24 @@ var PRESTIGE_TIERS = ACHIEVEMENT_RARITIES.map((rarity, index) => ({
|
|
|
6598
6609
|
minLevel: (index + 1) * LEVELS_PER_BAND
|
|
6599
6610
|
}));
|
|
6600
6611
|
var TOP_PRESTIGE_BAND = PRESTIGE_TIERS.length;
|
|
6612
|
+
var VM_HOST_MEMORY_SHARE = 0.75;
|
|
6613
|
+
var RELAY_RSS_ALLOWANCE_MIB = 128;
|
|
6614
|
+
var BASE_RELAY_SLOTS = 16;
|
|
6615
|
+
function relayAllowanceGiB(relaySlots = BASE_RELAY_SLOTS) {
|
|
6616
|
+
return Math.max(0, relaySlots - BASE_RELAY_SLOTS) * RELAY_RSS_ALLOWANCE_MIB / 1024;
|
|
6617
|
+
}
|
|
6618
|
+
function maximumGuestMemoryGiBWithin(budgetGiB, relaySlots = BASE_RELAY_SLOTS) {
|
|
6619
|
+
const guestBudget = budgetGiB - relayAllowanceGiB(relaySlots);
|
|
6620
|
+
if (!Number.isFinite(guestBudget) || guestBudget <= 0) return 0;
|
|
6621
|
+
return Math.max(0, Math.floor(Math.min(guestBudget - 1, guestBudget / 1.25)));
|
|
6622
|
+
}
|
|
6623
|
+
function maximumGuestMemoryGiB(hostBytes, relaySlots = BASE_RELAY_SLOTS) {
|
|
6624
|
+
if (!Number.isFinite(hostBytes) || hostBytes <= 0) return 0;
|
|
6625
|
+
return maximumGuestMemoryGiBWithin(vmMemoryBudgetGiB(hostBytes / 1024 ** 3), relaySlots);
|
|
6626
|
+
}
|
|
6627
|
+
function vmMemoryBudgetGiB(hostMemoryGiB) {
|
|
6628
|
+
return hostMemoryGiB * VM_HOST_MEMORY_SHARE;
|
|
6629
|
+
}
|
|
6601
6630
|
var CARD_TYPE_SURFACE = {
|
|
6602
6631
|
task: "board",
|
|
6603
6632
|
chat: "board",
|
|
@@ -6957,14 +6986,22 @@ async function readImageInventory(exec) {
|
|
|
6957
6986
|
}
|
|
6958
6987
|
return { images, referencedIds };
|
|
6959
6988
|
}
|
|
6989
|
+
var CRICTL_RMI_TIMEOUT = "120s";
|
|
6990
|
+
var RMI_EXEC_TIMEOUT_MS = 15e4;
|
|
6991
|
+
var DEADLINE_EXCEEDED = "context deadline exceeded";
|
|
6960
6992
|
async function pruneStrandedImages(exec, inventory, wantedImages) {
|
|
6961
6993
|
const candidates = selectStrandedImages(inventory, wantedImages);
|
|
6962
|
-
const result = { removed: 0, bytesReclaimed: 0, failures: [] };
|
|
6994
|
+
const result = { removed: 0, bytesReclaimed: 0, failures: [], pending: [] };
|
|
6963
6995
|
for (const candidate of candidates) {
|
|
6964
|
-
const removal = await exec("crictl", ["rmi", candidate.id]
|
|
6996
|
+
const removal = await exec("crictl", ["--timeout", CRICTL_RMI_TIMEOUT, "rmi", candidate.id], {
|
|
6997
|
+
timeoutMs: RMI_EXEC_TIMEOUT_MS
|
|
6998
|
+
});
|
|
6965
6999
|
if (removal.exitCode === 0) {
|
|
6966
7000
|
result.removed += 1;
|
|
6967
7001
|
result.bytesReclaimed += candidate.sizeBytes;
|
|
7002
|
+
} else if (`${removal.stderr}
|
|
7003
|
+
${removal.stdout}`.includes(DEADLINE_EXCEEDED)) {
|
|
7004
|
+
result.pending.push(candidate.id);
|
|
6968
7005
|
} else {
|
|
6969
7006
|
result.failures.push(`${candidate.id}: ${removal.stderr.trim().slice(0, 120)}`);
|
|
6970
7007
|
}
|
|
@@ -7074,6 +7111,8 @@ var STATE_DIR = "/var/lib/conveyor-k3";
|
|
|
7074
7111
|
var STATE_FILE = `${STATE_DIR}/state.json`;
|
|
7075
7112
|
var LOCAL_REGISTRY_DATA_DIR = `${STATE_DIR}/registry`;
|
|
7076
7113
|
var LOCAL_REGISTRY_MIRROR_RESTART_FILE = `${STATE_DIR}/registry-mirror-restart`;
|
|
7114
|
+
var LOCAL_REGISTRY_CREDENTIALS_FILE = `${STATE_DIR}/registry-credentials.json`;
|
|
7115
|
+
var LOCAL_REGISTRY_AUTH_STATE_FILE = `${STATE_DIR}/registry-auth.json`;
|
|
7077
7116
|
var RANCHER_DIR = "/etc/rancher/k3s";
|
|
7078
7117
|
var K3S_OWNER_CONFIG = `${RANCHER_DIR}/config.yaml`;
|
|
7079
7118
|
var K3S_CONFIG_DROPIN_DIR = `${RANCHER_DIR}/config.yaml.d`;
|
|
@@ -7123,6 +7162,16 @@ function upsertRegistryAuth(file, host, auth) {
|
|
|
7123
7162
|
next.configs = configs;
|
|
7124
7163
|
return next;
|
|
7125
7164
|
}
|
|
7165
|
+
function removeRegistryAuth(file, host) {
|
|
7166
|
+
const existing = file.configs?.[host];
|
|
7167
|
+
if (!existing?.auth) return file;
|
|
7168
|
+
const { auth: _auth, ...rest } = existing;
|
|
7169
|
+
const { [host]: _removed, ...others } = file.configs ?? {};
|
|
7170
|
+
const configs = Object.keys(rest).length > 0 ? { ...others, [host]: rest } : others;
|
|
7171
|
+
const next = { ...file, configs };
|
|
7172
|
+
if (Object.keys(configs).length === 0) delete next.configs;
|
|
7173
|
+
return next;
|
|
7174
|
+
}
|
|
7126
7175
|
function mirrorEndpoints(file, host) {
|
|
7127
7176
|
const entry = file.mirrors?.[host];
|
|
7128
7177
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
@@ -7179,6 +7228,85 @@ function managesK3s(desired, state) {
|
|
|
7179
7228
|
return desired.isolation === void 0 || state.k3sInstalledByConveyor === true;
|
|
7180
7229
|
}
|
|
7181
7230
|
|
|
7231
|
+
// src/lib/manifest-guard.ts
|
|
7232
|
+
import { isAlias, isMap, isScalar, parseAllDocuments } from "yaml";
|
|
7233
|
+
var ALLOWED_MANIFEST_KINDS = [
|
|
7234
|
+
"Namespace",
|
|
7235
|
+
"Role",
|
|
7236
|
+
"RoleBinding",
|
|
7237
|
+
"ClusterRole",
|
|
7238
|
+
"ClusterRoleBinding",
|
|
7239
|
+
"PriorityClass"
|
|
7240
|
+
];
|
|
7241
|
+
var ALLOWED = new Set(ALLOWED_MANIFEST_KINDS);
|
|
7242
|
+
var MISSING_KIND = "(missing kind)";
|
|
7243
|
+
var UNPARSEABLE = "(unparseable)";
|
|
7244
|
+
var LIST_SHAPED = "(list-shaped document)";
|
|
7245
|
+
function isListShaped(map) {
|
|
7246
|
+
return map.items.some(({ key }) => {
|
|
7247
|
+
if (isAlias(key)) return true;
|
|
7248
|
+
const name = isScalar(key) ? key.value : key;
|
|
7249
|
+
return typeof name === "string" && (name === "<<" || name.toLowerCase() === "items");
|
|
7250
|
+
});
|
|
7251
|
+
}
|
|
7252
|
+
function findDisallowedKinds(manifest) {
|
|
7253
|
+
let documents;
|
|
7254
|
+
try {
|
|
7255
|
+
documents = parseAllDocuments(manifest);
|
|
7256
|
+
} catch {
|
|
7257
|
+
return [UNPARSEABLE];
|
|
7258
|
+
}
|
|
7259
|
+
const disallowed = [];
|
|
7260
|
+
const record = (kind) => {
|
|
7261
|
+
if (!disallowed.includes(kind)) disallowed.push(kind);
|
|
7262
|
+
};
|
|
7263
|
+
for (const doc of documents) {
|
|
7264
|
+
if (doc.errors.length > 0) {
|
|
7265
|
+
record(UNPARSEABLE);
|
|
7266
|
+
continue;
|
|
7267
|
+
}
|
|
7268
|
+
if (doc.contents === null || doc.contents === void 0) continue;
|
|
7269
|
+
if (isScalar(doc.contents) && doc.contents.value === null) continue;
|
|
7270
|
+
if (!isMap(doc.contents)) {
|
|
7271
|
+
record(MISSING_KIND);
|
|
7272
|
+
continue;
|
|
7273
|
+
}
|
|
7274
|
+
if (isListShaped(doc.contents)) {
|
|
7275
|
+
record(LIST_SHAPED);
|
|
7276
|
+
continue;
|
|
7277
|
+
}
|
|
7278
|
+
const kind = doc.get("kind");
|
|
7279
|
+
if (typeof kind !== "string" || kind.length === 0) {
|
|
7280
|
+
record(MISSING_KIND);
|
|
7281
|
+
continue;
|
|
7282
|
+
}
|
|
7283
|
+
if (!ALLOWED.has(kind)) record(kind);
|
|
7284
|
+
}
|
|
7285
|
+
return disallowed;
|
|
7286
|
+
}
|
|
7287
|
+
function withoutKind(manifest, kind) {
|
|
7288
|
+
return parseAllDocuments(manifest).filter((doc) => !(isMap(doc.contents) && doc.get("kind") === kind)).map((doc) => doc.toString()).join("---\n");
|
|
7289
|
+
}
|
|
7290
|
+
function declaresPriorityClass(manifest, name, value) {
|
|
7291
|
+
return parseAllDocuments(manifest).some(
|
|
7292
|
+
(doc) => isMap(doc.contents) && doc.get("kind") === "PriorityClass" && doc.getIn(["metadata", "name"]) === name && doc.get("value") === value
|
|
7293
|
+
);
|
|
7294
|
+
}
|
|
7295
|
+
|
|
7296
|
+
// src/daemon/cluster-ops.ts
|
|
7297
|
+
async function kubectlApply(exec, manifest) {
|
|
7298
|
+
await runOrThrow(exec, "k3s", ["kubectl", "apply", "-f", "-"], {
|
|
7299
|
+
stdin: manifest,
|
|
7300
|
+
timeoutMs: 6e4
|
|
7301
|
+
});
|
|
7302
|
+
}
|
|
7303
|
+
function mayDisrupt(runningConveyorPods) {
|
|
7304
|
+
return runningConveyorPods === 0;
|
|
7305
|
+
}
|
|
7306
|
+
function busyReason(runningConveyorPods) {
|
|
7307
|
+
return runningConveyorPods === null ? "the number of running Conveyor pods could not be read" : `${runningConveyorPods} Conveyor pod(s) are running`;
|
|
7308
|
+
}
|
|
7309
|
+
|
|
7182
7310
|
// src/lib/k3s-config.ts
|
|
7183
7311
|
var IMAGE_GC_HIGH = "image-gc-high-threshold";
|
|
7184
7312
|
var IMAGE_GC_LOW = "image-gc-low-threshold";
|
|
@@ -7263,76 +7391,345 @@ function renderK3sDropIn(kubelet, ownerConfig, options = {}) {
|
|
|
7263
7391
|
}
|
|
7264
7392
|
return { content: [...lines2, ""].join("\n"), skipped };
|
|
7265
7393
|
}
|
|
7266
|
-
var renderKubeletDropIn = renderK3sDropIn;
|
|
7267
7394
|
|
|
7268
|
-
// src/
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
"
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
"
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7395
|
+
// src/daemon/pod-cpu-quota.ts
|
|
7396
|
+
var POD_SLICE = "kubepods.slice";
|
|
7397
|
+
function cpuMillicores(quantity) {
|
|
7398
|
+
const checked = assertQuantity(quantity, "reserveCpu");
|
|
7399
|
+
const match = /^(\d+(?:\.\d+)?)(m?)$/.exec(checked);
|
|
7400
|
+
if (!match?.[1])
|
|
7401
|
+
throw new Error(`Invalid reserveCpu: ${JSON.stringify(checked)} is not a CPU amount`);
|
|
7402
|
+
const amount = Number.parseFloat(match[1]);
|
|
7403
|
+
return Math.round(match[2] === "m" ? amount : amount * 1e3);
|
|
7404
|
+
}
|
|
7405
|
+
function podCpuQuotaPercent(cores, reservedMillicores) {
|
|
7406
|
+
const left = cores * 1e3 - reservedMillicores;
|
|
7407
|
+
if (left < 10) return null;
|
|
7408
|
+
return Math.floor(left / 10);
|
|
7409
|
+
}
|
|
7410
|
+
function setQuota(exec, value) {
|
|
7411
|
+
return exec("systemctl", ["set-property", POD_SLICE, `CPUQuota=${value}`]);
|
|
7412
|
+
}
|
|
7413
|
+
async function liftPodCpuQuota(exec) {
|
|
7414
|
+
const shown = await exec("systemctl", ["show", POD_SLICE, "-p", "CPUQuotaPerSecUSec", "--value"]);
|
|
7415
|
+
if (shown.exitCode !== 0 || ["", "infinity"].includes(shown.stdout.trim())) return null;
|
|
7416
|
+
const cleared = await setQuota(exec, "");
|
|
7417
|
+
return cleared.exitCode === 0 ? null : cleared.stderr.trim().slice(0, 200);
|
|
7418
|
+
}
|
|
7419
|
+
async function reconcilePodCpuQuota(exec, desired, warnings) {
|
|
7420
|
+
const reserve = desired.kubelet.systemReserved?.cpu;
|
|
7421
|
+
if (!reserve) {
|
|
7422
|
+
const failure = await liftPodCpuQuota(exec);
|
|
7423
|
+
if (failure) warnings.push(`Could not lift the CPU cap on ${POD_SLICE}: ${failure}`);
|
|
7424
|
+
return void 0;
|
|
7287
7425
|
}
|
|
7288
|
-
const
|
|
7289
|
-
const
|
|
7290
|
-
|
|
7426
|
+
const reserved = cpuMillicores(reserve);
|
|
7427
|
+
const state = await exec("systemctl", ["show", POD_SLICE, "-p", "ActiveState", "--value"]);
|
|
7428
|
+
if (state.exitCode !== 0 || state.stdout.trim() !== "active") {
|
|
7429
|
+
warnings.push(
|
|
7430
|
+
`Pods do not run under ${POD_SLICE} (k3s is not using the systemd cgroup driver), so the CPU you keep for this machine is weighted, not capped.`
|
|
7431
|
+
);
|
|
7432
|
+
return false;
|
|
7433
|
+
}
|
|
7434
|
+
const nproc = await exec("nproc", []);
|
|
7435
|
+
const cores = Number.parseInt(nproc.stdout.trim(), 10);
|
|
7436
|
+
if (nproc.exitCode !== 0 || !Number.isInteger(cores) || cores <= 0) {
|
|
7437
|
+
warnings.push(
|
|
7438
|
+
"Could not count this machine's CPUs, so the CPU you keep is weighted, not capped."
|
|
7439
|
+
);
|
|
7440
|
+
return false;
|
|
7441
|
+
}
|
|
7442
|
+
const percent = podCpuQuotaPercent(cores, reserved);
|
|
7443
|
+
if (percent === null) {
|
|
7444
|
+
warnings.push(
|
|
7445
|
+
`The CPU you keep (${reserve}) leaves no CPU for builds on this ${cores}-CPU machine. Lower it in your Conveyor profile.`
|
|
7446
|
+
);
|
|
7447
|
+
return false;
|
|
7448
|
+
}
|
|
7449
|
+
const set = await setQuota(exec, `${percent}%`);
|
|
7450
|
+
if (set.exitCode !== 0) {
|
|
7451
|
+
warnings.push(
|
|
7452
|
+
`Could not cap pod CPU at ${percent / 100} CPUs: ${set.stderr.trim().slice(0, 200)}`
|
|
7453
|
+
);
|
|
7454
|
+
return false;
|
|
7455
|
+
}
|
|
7456
|
+
return true;
|
|
7457
|
+
}
|
|
7458
|
+
|
|
7459
|
+
// src/daemon/kubelet-upkeep.ts
|
|
7460
|
+
function ownerRestartWarning(change) {
|
|
7461
|
+
return `${change} Conveyor did not install this server's k3s, so it never restarts it: run \`sudo systemctl restart k3s\` when no build runs.`;
|
|
7462
|
+
}
|
|
7463
|
+
async function reconcileKubelet(ctx, desired, result, managed) {
|
|
7464
|
+
const ownerConfig = await ctx.fs.readFile(K3S_OWNER_CONFIG);
|
|
7465
|
+
const rendered = renderK3sDropIn(desired.kubelet, ownerConfig, { disableComponents: managed });
|
|
7466
|
+
result.skippedKubeletFlags = rendered.skipped;
|
|
7467
|
+
result.effectiveImageGcHighThreshold = rendered.skipped.includes("image-gc-high-threshold") ? ownerImageGcHighThreshold(ownerConfig) : desired.kubelet.imageGcHighThreshold;
|
|
7468
|
+
for (const flag of rendered.skipped) {
|
|
7469
|
+
result.warnings.push(
|
|
7470
|
+
`Left \`${flag}\` to your own /etc/rancher/k3s/config.yaml \u2014 Conveyor does not override a setting you chose.`
|
|
7471
|
+
);
|
|
7472
|
+
}
|
|
7473
|
+
const current = await ctx.fs.readFile(K3S_CONFIG_DROPIN);
|
|
7474
|
+
if (rendered.content === null) {
|
|
7475
|
+
if (current !== null) await ctx.fs.removeFile(K3S_CONFIG_DROPIN);
|
|
7476
|
+
return;
|
|
7477
|
+
}
|
|
7478
|
+
if (current === rendered.content) return;
|
|
7479
|
+
await ctx.fs.ensureDir(K3S_CONFIG_DROPIN.slice(0, K3S_CONFIG_DROPIN.lastIndexOf("/")));
|
|
7480
|
+
await ctx.fs.writeFile(K3S_CONFIG_DROPIN, rendered.content, 420);
|
|
7481
|
+
if (!managed) {
|
|
7482
|
+
result.warnings.push(ownerRestartWarning("Kubelet settings changed."));
|
|
7483
|
+
return;
|
|
7484
|
+
}
|
|
7485
|
+
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
7486
|
+
result.warnings.push(
|
|
7487
|
+
`Kubelet settings changed but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
7488
|
+
);
|
|
7489
|
+
return;
|
|
7490
|
+
}
|
|
7491
|
+
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
7492
|
+
if (restart.exitCode === 0) {
|
|
7493
|
+
result.restartedK3s = true;
|
|
7494
|
+
} else {
|
|
7495
|
+
result.warnings.push(
|
|
7496
|
+
`Could not restart k3s to apply new kubelet settings: ${restart.stderr.trim().slice(0, 200)}`
|
|
7497
|
+
);
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
|
|
7501
|
+
// src/daemon/local-registry-upkeep.ts
|
|
7502
|
+
import { createHash as createHash3 } from "crypto";
|
|
7503
|
+
|
|
7504
|
+
// src/lib/local-registry.ts
|
|
7505
|
+
import { createHash as createHash2 } from "crypto";
|
|
7506
|
+
|
|
7507
|
+
// src/lib/registry-credentials.ts
|
|
7508
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
7509
|
+
|
|
7510
|
+
// src/lib/bcrypt.ts
|
|
7511
|
+
var P_WORDS = 18;
|
|
7512
|
+
var S_WORDS = 1024;
|
|
7513
|
+
var MAGIC = "OrpheanBeholderScryDoubt";
|
|
7514
|
+
var ALPHABET = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
7515
|
+
var MAX_KEY_BYTES = 72;
|
|
7516
|
+
var BCRYPT_SALT_BYTES = 16;
|
|
7517
|
+
function scaledAtanInverse(x, one) {
|
|
7518
|
+
const xSquared = x * x;
|
|
7519
|
+
let power = one / x;
|
|
7520
|
+
let sum = 0n;
|
|
7521
|
+
for (let n = 0n; power !== 0n; n++) {
|
|
7522
|
+
const term = power / (2n * n + 1n);
|
|
7523
|
+
sum = n % 2n === 0n ? sum + term : sum - term;
|
|
7524
|
+
power /= xSquared;
|
|
7525
|
+
}
|
|
7526
|
+
return sum;
|
|
7527
|
+
}
|
|
7528
|
+
function piFractionWords(count) {
|
|
7529
|
+
const guard = 64n;
|
|
7530
|
+
const bits = BigInt(count * 32) + guard;
|
|
7531
|
+
const one = 1n << bits;
|
|
7532
|
+
const pi = 16n * scaledAtanInverse(5n, one) - 4n * scaledAtanInverse(239n, one);
|
|
7533
|
+
const fraction = pi - 3n * one >> guard;
|
|
7534
|
+
const words = new Uint32Array(count);
|
|
7535
|
+
for (let index = 0; index < count; index++) {
|
|
7536
|
+
words[index] = Number(fraction >> BigInt(32 * (count - 1 - index)) & 0xffffffffn);
|
|
7537
|
+
}
|
|
7538
|
+
return words;
|
|
7539
|
+
}
|
|
7540
|
+
var initialState = null;
|
|
7541
|
+
function blowfishInitialState() {
|
|
7542
|
+
if (!initialState) {
|
|
7543
|
+
const words = piFractionWords(P_WORDS + S_WORDS);
|
|
7544
|
+
initialState = { p: words.slice(0, P_WORDS), s: words.slice(P_WORDS) };
|
|
7545
|
+
}
|
|
7546
|
+
return initialState;
|
|
7547
|
+
}
|
|
7548
|
+
function word(array, index) {
|
|
7549
|
+
return array[index] ?? 0;
|
|
7550
|
+
}
|
|
7551
|
+
function round(s, x) {
|
|
7552
|
+
const mixed = word(s, x >>> 24) + word(s, 256 + (x >>> 16 & 255)) ^ word(s, 512 + (x >>> 8 & 255));
|
|
7553
|
+
return mixed + word(s, 768 + (x & 255)) | 0;
|
|
7554
|
+
}
|
|
7555
|
+
function encipher(state, data, offset) {
|
|
7556
|
+
const { p, s } = state;
|
|
7557
|
+
let left = word(data, offset) ^ word(p, 0);
|
|
7558
|
+
let right = word(data, offset + 1);
|
|
7559
|
+
for (let index = 1; index <= 16; index += 2) {
|
|
7560
|
+
right ^= round(s, left) ^ word(p, index);
|
|
7561
|
+
left ^= round(s, right) ^ word(p, index + 1);
|
|
7562
|
+
}
|
|
7563
|
+
data[offset] = right ^ word(p, 17);
|
|
7564
|
+
data[offset + 1] = left;
|
|
7565
|
+
}
|
|
7566
|
+
function wordStream(bytes) {
|
|
7567
|
+
let position = 0;
|
|
7568
|
+
return () => {
|
|
7569
|
+
let value = 0;
|
|
7570
|
+
for (let index = 0; index < 4; index++) {
|
|
7571
|
+
value = value << 8 | (bytes[position] ?? 0);
|
|
7572
|
+
position = (position + 1) % bytes.length;
|
|
7573
|
+
}
|
|
7574
|
+
return value >>> 0;
|
|
7291
7575
|
};
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7576
|
+
}
|
|
7577
|
+
function expandKey(state, key, salt) {
|
|
7578
|
+
const nextKeyWord = wordStream(key);
|
|
7579
|
+
for (let index = 0; index < P_WORDS; index++) {
|
|
7580
|
+
state.p[index] = word(state.p, index) ^ nextKeyWord();
|
|
7581
|
+
}
|
|
7582
|
+
const nextSaltWord = salt ? wordStream(salt) : () => 0;
|
|
7583
|
+
const block = new Uint32Array(2);
|
|
7584
|
+
const fill = (target, length) => {
|
|
7585
|
+
for (let index = 0; index < length; index += 2) {
|
|
7586
|
+
block[0] = word(block, 0) ^ nextSaltWord();
|
|
7587
|
+
block[1] = word(block, 1) ^ nextSaltWord();
|
|
7588
|
+
encipher(state, block, 0);
|
|
7589
|
+
target[index] = word(block, 0);
|
|
7590
|
+
target[index + 1] = word(block, 1);
|
|
7296
7591
|
}
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7592
|
+
};
|
|
7593
|
+
fill(state.p, P_WORDS);
|
|
7594
|
+
fill(state.s, S_WORDS);
|
|
7595
|
+
}
|
|
7596
|
+
function encodeBase64(bytes, length) {
|
|
7597
|
+
let out = "";
|
|
7598
|
+
let offset = 0;
|
|
7599
|
+
while (offset < length) {
|
|
7600
|
+
let first = bytes[offset++] ?? 0;
|
|
7601
|
+
out += ALPHABET[first >> 2];
|
|
7602
|
+
first = (first & 3) << 4;
|
|
7603
|
+
if (offset >= length) {
|
|
7604
|
+
out += ALPHABET[first];
|
|
7605
|
+
break;
|
|
7302
7606
|
}
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7607
|
+
let second = bytes[offset++] ?? 0;
|
|
7608
|
+
out += ALPHABET[first | second >> 4];
|
|
7609
|
+
first = (second & 15) << 2;
|
|
7610
|
+
if (offset >= length) {
|
|
7611
|
+
out += ALPHABET[first];
|
|
7612
|
+
break;
|
|
7307
7613
|
}
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7614
|
+
second = bytes[offset++] ?? 0;
|
|
7615
|
+
out += ALPHABET[first | second >> 6];
|
|
7616
|
+
out += ALPHABET[second & 63];
|
|
7617
|
+
}
|
|
7618
|
+
return out;
|
|
7619
|
+
}
|
|
7620
|
+
function bcryptHash(password, cost, salt) {
|
|
7621
|
+
if (!Number.isInteger(cost) || cost < 4 || cost > 31) {
|
|
7622
|
+
throw new Error(`bcrypt cost must be an integer from 4 to 31, not ${cost}`);
|
|
7623
|
+
}
|
|
7624
|
+
if (salt.length !== BCRYPT_SALT_BYTES) {
|
|
7625
|
+
throw new Error(`bcrypt needs a ${BCRYPT_SALT_BYTES}-byte salt`);
|
|
7626
|
+
}
|
|
7627
|
+
const passwordBytes = new TextEncoder().encode(password).slice(0, MAX_KEY_BYTES);
|
|
7628
|
+
const key = new Uint8Array(passwordBytes.length + 1);
|
|
7629
|
+
key.set(passwordBytes);
|
|
7630
|
+
const base = blowfishInitialState();
|
|
7631
|
+
const state = { p: base.p.slice(), s: base.s.slice() };
|
|
7632
|
+
expandKey(state, key, salt);
|
|
7633
|
+
for (let rounds = 2 ** cost; rounds > 0; rounds--) {
|
|
7634
|
+
expandKey(state, key, null);
|
|
7635
|
+
expandKey(state, salt, null);
|
|
7636
|
+
}
|
|
7637
|
+
const magic = new TextEncoder().encode(MAGIC);
|
|
7638
|
+
const nextMagicWord = wordStream(magic);
|
|
7639
|
+
const block = Uint32Array.from({ length: 6 }, () => nextMagicWord());
|
|
7640
|
+
for (let pass = 0; pass < 64; pass++) {
|
|
7641
|
+
for (let offset = 0; offset < 6; offset += 2) encipher(state, block, offset);
|
|
7642
|
+
}
|
|
7643
|
+
const digest = new Uint8Array(24);
|
|
7644
|
+
block.forEach((value, index) => {
|
|
7645
|
+
digest[index * 4] = value >>> 24;
|
|
7646
|
+
digest[index * 4 + 1] = value >>> 16 & 255;
|
|
7647
|
+
digest[index * 4 + 2] = value >>> 8 & 255;
|
|
7648
|
+
digest[index * 4 + 3] = value & 255;
|
|
7649
|
+
});
|
|
7650
|
+
const costText = String(cost).padStart(2, "0");
|
|
7651
|
+
return `$2y$${costText}$${encodeBase64(salt, BCRYPT_SALT_BYTES)}${encodeBase64(digest, 23)}`;
|
|
7311
7652
|
}
|
|
7312
7653
|
|
|
7313
|
-
// src/
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7654
|
+
// src/lib/registry-credentials.ts
|
|
7655
|
+
var LOCAL_REGISTRY_USERNAME = "conveyor";
|
|
7656
|
+
var BCRYPT_COST = 5;
|
|
7657
|
+
var USERNAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
7658
|
+
var PASSWORD = /^[A-Za-z0-9_-]{32,128}$/;
|
|
7659
|
+
function generateRegistryCredentials(random = randomBytes2) {
|
|
7660
|
+
const username = LOCAL_REGISTRY_USERNAME;
|
|
7661
|
+
const password = Buffer.from(random(32)).toString("base64url");
|
|
7662
|
+
const hash = bcryptHash(password, BCRYPT_COST, random(BCRYPT_SALT_BYTES));
|
|
7663
|
+
return { username, password, htpasswd: `${username}:${hash}` };
|
|
7664
|
+
}
|
|
7665
|
+
function parseRegistryCredentials(raw) {
|
|
7666
|
+
if (!raw) return null;
|
|
7667
|
+
try {
|
|
7668
|
+
const value = JSON.parse(raw);
|
|
7669
|
+
const { username, password, htpasswd } = value ?? {};
|
|
7670
|
+
if (typeof username !== "string" || !USERNAME.test(username)) return null;
|
|
7671
|
+
if (typeof password !== "string" || !PASSWORD.test(password)) return null;
|
|
7672
|
+
if (typeof htpasswd !== "string" || !htpasswd.startsWith(`${username}:$2`)) return null;
|
|
7673
|
+
if (htpasswd.includes("\n")) return null;
|
|
7674
|
+
return { username, password, htpasswd };
|
|
7675
|
+
} catch {
|
|
7676
|
+
return null;
|
|
7677
|
+
}
|
|
7319
7678
|
}
|
|
7320
|
-
function
|
|
7321
|
-
|
|
7679
|
+
async function loadOrCreateRegistryCredentials(fs, random) {
|
|
7680
|
+
const stored = parseRegistryCredentials(await fs.readFile(LOCAL_REGISTRY_CREDENTIALS_FILE));
|
|
7681
|
+
if (stored) return stored;
|
|
7682
|
+
const created = generateRegistryCredentials(random);
|
|
7683
|
+
await fs.writeFile(LOCAL_REGISTRY_CREDENTIALS_FILE, `${JSON.stringify(created)}
|
|
7684
|
+
`, 384);
|
|
7685
|
+
return created;
|
|
7322
7686
|
}
|
|
7323
|
-
function
|
|
7324
|
-
return
|
|
7687
|
+
function credentialFingerprint(credentials) {
|
|
7688
|
+
return createHash("sha256").update(`${credentials.username}:${credentials.password}`).digest("hex").slice(0, 16);
|
|
7689
|
+
}
|
|
7690
|
+
function basicAuthorization(credentials) {
|
|
7691
|
+
return `Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64")}`;
|
|
7692
|
+
}
|
|
7693
|
+
function registryDockerConfig(credentials, host) {
|
|
7694
|
+
const auth = Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64");
|
|
7695
|
+
return JSON.stringify({ auths: { [host]: { auth } } });
|
|
7696
|
+
}
|
|
7697
|
+
function parseRegistryAuthState(raw) {
|
|
7698
|
+
if (!raw) return null;
|
|
7699
|
+
try {
|
|
7700
|
+
const value = JSON.parse(raw);
|
|
7701
|
+
if (typeof value?.credential !== "string") return null;
|
|
7702
|
+
const writtenAt = typeof value.writtenAt === "string" && !Number.isNaN(Date.parse(value.writtenAt)) ? value.writtenAt : null;
|
|
7703
|
+
return { credential: value.credential, writtenAt, enforced: value.enforced === true };
|
|
7704
|
+
} catch {
|
|
7705
|
+
return null;
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
async function registryAuthEnforced(readFile3) {
|
|
7709
|
+
return parseRegistryAuthState(await readFile3(LOCAL_REGISTRY_AUTH_STATE_FILE))?.enforced === true;
|
|
7710
|
+
}
|
|
7711
|
+
function withRegistryCredentials(fetchImpl, readFile3) {
|
|
7712
|
+
let credentials = null;
|
|
7713
|
+
return async (url, init) => {
|
|
7714
|
+
credentials ??= readFile3(LOCAL_REGISTRY_CREDENTIALS_FILE).then(parseRegistryCredentials);
|
|
7715
|
+
const stored = await credentials;
|
|
7716
|
+
if (!stored) return fetchImpl(url, init);
|
|
7717
|
+
const headers = new Headers(init?.headers);
|
|
7718
|
+
headers.set("Authorization", basicAuthorization(stored));
|
|
7719
|
+
return fetchImpl(url, { ...init, headers });
|
|
7720
|
+
};
|
|
7325
7721
|
}
|
|
7326
|
-
|
|
7327
|
-
// src/daemon/local-registry-upkeep.ts
|
|
7328
|
-
import { createHash } from "crypto";
|
|
7329
7722
|
|
|
7330
7723
|
// src/lib/local-registry.ts
|
|
7331
7724
|
var LOCAL_REGISTRY_IMAGE = "docker.io/library/registry:3.1.2@sha256:c87f33837722a100572e95d7dc4bf539fc42cf68202b13c3bc03c0ff54c3a649";
|
|
7332
7725
|
var LOCAL_REGISTRY_CONFIG = "/etc/distribution/config.yml";
|
|
7333
7726
|
var LOCAL_REGISTRY_GC_JOB = "conveyor-registry-gc";
|
|
7727
|
+
var LOCAL_REGISTRY_HTPASSWD_SECRET = "conveyor-registry-htpasswd";
|
|
7728
|
+
var HTPASSWD_KEY = "htpasswd";
|
|
7729
|
+
var AUTH_MOUNT = "/etc/distribution/auth";
|
|
7334
7730
|
var LOCAL_REGISTRY_SELECTOR = { "app.kubernetes.io/name": PERSONAL_LOCAL_REGISTRY_NAME };
|
|
7335
7731
|
var MANAGED_BY = { "app.kubernetes.io/managed-by": "conveyor-k3" };
|
|
7732
|
+
var AUTH_CHECKSUM_ANNOTATION = "conveyor.rallycry.com/registry-auth";
|
|
7336
7733
|
var DATA_MOUNT = "/var/lib/registry";
|
|
7337
7734
|
var REGISTRY_ENV = [
|
|
7338
7735
|
{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" },
|
|
@@ -7347,6 +7744,23 @@ var VOLUME_MOUNTS = [
|
|
|
7347
7744
|
{ name: "data", mountPath: DATA_MOUNT },
|
|
7348
7745
|
{ name: "tmp", mountPath: "/tmp" }
|
|
7349
7746
|
];
|
|
7747
|
+
var AUTH_ENV = [
|
|
7748
|
+
{ name: "REGISTRY_AUTH", value: "htpasswd" },
|
|
7749
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_REALM", value: "conveyor" },
|
|
7750
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_PATH", value: `${AUTH_MOUNT}/${HTPASSWD_KEY}` }
|
|
7751
|
+
];
|
|
7752
|
+
var AUTH_VOLUME = {
|
|
7753
|
+
name: "auth",
|
|
7754
|
+
secret: {
|
|
7755
|
+
secretName: LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
7756
|
+
items: [{ key: HTPASSWD_KEY, path: HTPASSWD_KEY }],
|
|
7757
|
+
defaultMode: 256
|
|
7758
|
+
}
|
|
7759
|
+
};
|
|
7760
|
+
var AUTH_VOLUME_MOUNT = { name: "auth", mountPath: AUTH_MOUNT, readOnly: true };
|
|
7761
|
+
function htpasswdChecksum(htpasswd) {
|
|
7762
|
+
return createHash2("sha256").update(htpasswd).digest("hex").slice(0, 16);
|
|
7763
|
+
}
|
|
7350
7764
|
var VOLUMES = [
|
|
7351
7765
|
{ name: "data", hostPath: { path: LOCAL_REGISTRY_DATA_DIR, type: "Directory" } },
|
|
7352
7766
|
{ name: "tmp", emptyDir: { sizeLimit: "64Mi" } }
|
|
@@ -7383,7 +7797,34 @@ function registryNetworkPolicy(metadata) {
|
|
|
7383
7797
|
}
|
|
7384
7798
|
};
|
|
7385
7799
|
}
|
|
7386
|
-
function
|
|
7800
|
+
function renderLocalRegistrySecrets(credentials) {
|
|
7801
|
+
const encode = (value) => Buffer.from(value).toString("base64");
|
|
7802
|
+
const secret = (namespace, name, key, value) => ({
|
|
7803
|
+
apiVersion: "v1",
|
|
7804
|
+
kind: "Secret",
|
|
7805
|
+
metadata: { name, namespace, labels: { ...MANAGED_BY } },
|
|
7806
|
+
type: "Opaque",
|
|
7807
|
+
data: { [key]: encode(value) }
|
|
7808
|
+
});
|
|
7809
|
+
const items = [
|
|
7810
|
+
secret(
|
|
7811
|
+
PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
7812
|
+
LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
7813
|
+
HTPASSWD_KEY,
|
|
7814
|
+
`${credentials.htpasswd}
|
|
7815
|
+
`
|
|
7816
|
+
),
|
|
7817
|
+
secret(
|
|
7818
|
+
PERSONAL_BAKE_NAMESPACE,
|
|
7819
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET,
|
|
7820
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY,
|
|
7821
|
+
registryDockerConfig(credentials, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
7822
|
+
)
|
|
7823
|
+
];
|
|
7824
|
+
return `${JSON.stringify({ apiVersion: "v1", kind: "List", items }, null, 2)}
|
|
7825
|
+
`;
|
|
7826
|
+
}
|
|
7827
|
+
function renderLocalRegistryManifests(auth = null) {
|
|
7387
7828
|
const metadata = {
|
|
7388
7829
|
name: PERSONAL_LOCAL_REGISTRY_NAME,
|
|
7389
7830
|
namespace: PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
@@ -7399,15 +7840,19 @@ function renderLocalRegistryManifests() {
|
|
|
7399
7840
|
strategy: { type: "Recreate" },
|
|
7400
7841
|
selector: { matchLabels: LOCAL_REGISTRY_SELECTOR },
|
|
7401
7842
|
template: {
|
|
7402
|
-
metadata: {
|
|
7843
|
+
metadata: {
|
|
7844
|
+
labels: metadata.labels,
|
|
7845
|
+
...auth ? { annotations: { [AUTH_CHECKSUM_ANNOTATION]: htpasswdChecksum(auth.htpasswd) } } : {}
|
|
7846
|
+
},
|
|
7403
7847
|
spec: {
|
|
7404
7848
|
...POD_BASE,
|
|
7849
|
+
...auth ? { volumes: [...VOLUMES, AUTH_VOLUME] } : {},
|
|
7405
7850
|
containers: [
|
|
7406
7851
|
{
|
|
7407
7852
|
name: "registry",
|
|
7408
7853
|
image: LOCAL_REGISTRY_IMAGE,
|
|
7409
7854
|
imagePullPolicy: "IfNotPresent",
|
|
7410
|
-
env: REGISTRY_ENV,
|
|
7855
|
+
env: auth ? [...REGISTRY_ENV, ...AUTH_ENV] : REGISTRY_ENV,
|
|
7411
7856
|
ports: [
|
|
7412
7857
|
{
|
|
7413
7858
|
name: "registry",
|
|
@@ -7419,8 +7864,10 @@ function renderLocalRegistryManifests() {
|
|
|
7419
7864
|
protocol: "TCP"
|
|
7420
7865
|
}
|
|
7421
7866
|
],
|
|
7867
|
+
// A TCP check: once the credential is enforced, `/v2/` answers
|
|
7868
|
+
// 401 to a probe that does not carry it.
|
|
7422
7869
|
readinessProbe: {
|
|
7423
|
-
|
|
7870
|
+
tcpSocket: { port: PERSONAL_LOCAL_REGISTRY_PORT },
|
|
7424
7871
|
periodSeconds: 10
|
|
7425
7872
|
},
|
|
7426
7873
|
resources: {
|
|
@@ -7428,7 +7875,7 @@ function renderLocalRegistryManifests() {
|
|
|
7428
7875
|
limits: { memory: "512Mi" }
|
|
7429
7876
|
},
|
|
7430
7877
|
securityContext: CONTAINER_SECURITY,
|
|
7431
|
-
volumeMounts: VOLUME_MOUNTS
|
|
7878
|
+
volumeMounts: auth ? [...VOLUME_MOUNTS, AUTH_VOLUME_MOUNT] : VOLUME_MOUNTS
|
|
7432
7879
|
}
|
|
7433
7880
|
]
|
|
7434
7881
|
}
|
|
@@ -7519,11 +7966,18 @@ async function registryAnswers(fetchImpl = fetch) {
|
|
|
7519
7966
|
}
|
|
7520
7967
|
async function localRegistryReady(ports) {
|
|
7521
7968
|
const [answers, mirrored] = await Promise.all([
|
|
7522
|
-
registryAnswers(ports.fetch),
|
|
7969
|
+
registryAnswers(withRegistryCredentials(ports.fetch ?? fetch, ports.readFile)),
|
|
7523
7970
|
mirrorLoaded(ports.readFile)
|
|
7524
7971
|
]);
|
|
7525
7972
|
return answers && mirrored;
|
|
7526
7973
|
}
|
|
7974
|
+
async function localRegistryStatus(ports) {
|
|
7975
|
+
const [ready, authEnforced] = await Promise.all([
|
|
7976
|
+
localRegistryReady(ports),
|
|
7977
|
+
registryAuthEnforced(ports.readFile)
|
|
7978
|
+
]);
|
|
7979
|
+
return { ready, authEnforced };
|
|
7980
|
+
}
|
|
7527
7981
|
|
|
7528
7982
|
// src/daemon/registry-gc-lock.ts
|
|
7529
7983
|
var REGISTRY_GC_MARK = "conveyor.rallycry.com/registry-gc";
|
|
@@ -7614,6 +8068,59 @@ async function deleteSweepJob(exec) {
|
|
|
7614
8068
|
return result.exitCode === 0;
|
|
7615
8069
|
}
|
|
7616
8070
|
|
|
8071
|
+
// src/daemon/registry-auth.ts
|
|
8072
|
+
var LOCAL_REGISTRY_AUTH_HOSTS = [
|
|
8073
|
+
PERSONAL_LOCAL_REGISTRY_HOST,
|
|
8074
|
+
new URL(PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT).host
|
|
8075
|
+
];
|
|
8076
|
+
function registryAuthConfigured(file, credentials) {
|
|
8077
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.every((host) => {
|
|
8078
|
+
const auth = file.configs?.[host]?.auth;
|
|
8079
|
+
return auth?.username === credentials.username && auth.password === credentials.password && auth.auth === void 0 && auth.identitytoken === void 0;
|
|
8080
|
+
});
|
|
8081
|
+
}
|
|
8082
|
+
function withLocalRegistryAuth(file, credentials) {
|
|
8083
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
8084
|
+
(next, host) => upsertRegistryAuth(next, host, {
|
|
8085
|
+
username: credentials.username,
|
|
8086
|
+
password: credentials.password
|
|
8087
|
+
}),
|
|
8088
|
+
file
|
|
8089
|
+
);
|
|
8090
|
+
}
|
|
8091
|
+
function monotonicNowUs() {
|
|
8092
|
+
return Number(process.hrtime.bigint() / 1000n);
|
|
8093
|
+
}
|
|
8094
|
+
async function k3sStartedAtMs(exec, nowMs, monotonicNow = monotonicNowUs) {
|
|
8095
|
+
const result = await exec(
|
|
8096
|
+
"systemctl",
|
|
8097
|
+
["show", "k3s", "--property=ActiveEnterTimestampMonotonic", "--value"],
|
|
8098
|
+
{ timeoutMs: 1e4 }
|
|
8099
|
+
).catch(() => null);
|
|
8100
|
+
if (!result || result.exitCode !== 0) return null;
|
|
8101
|
+
const startedUs = Number.parseInt(result.stdout.trim(), 10);
|
|
8102
|
+
if (!Number.isFinite(startedUs) || startedUs <= 0) return null;
|
|
8103
|
+
return nowMs - (monotonicNow() - startedUs) / 1e3;
|
|
8104
|
+
}
|
|
8105
|
+
async function settleRegistryAuth(ctx, credentials, written) {
|
|
8106
|
+
const now = (ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
8107
|
+
const raw = await ctx.fs.readFile(LOCAL_REGISTRY_AUTH_STATE_FILE);
|
|
8108
|
+
const credential = credentialFingerprint(credentials);
|
|
8109
|
+
const stored = parseRegistryAuthState(raw);
|
|
8110
|
+
const state = stored?.credential === credential ? stored : { credential, writtenAt: null, enforced: false };
|
|
8111
|
+
if (!state.enforced && (written || state.writtenAt === null)) {
|
|
8112
|
+
state.writtenAt = now.toISOString();
|
|
8113
|
+
}
|
|
8114
|
+
if (!state.enforced && state.writtenAt !== null) {
|
|
8115
|
+
const startedAt = await k3sStartedAtMs(ctx.exec, now.getTime(), ctx.monotonicNowUs);
|
|
8116
|
+
if (startedAt !== null && startedAt > Date.parse(state.writtenAt)) state.enforced = true;
|
|
8117
|
+
}
|
|
8118
|
+
const next = `${JSON.stringify(state)}
|
|
8119
|
+
`;
|
|
8120
|
+
if (next !== raw) await ctx.fs.writeFile(LOCAL_REGISTRY_AUTH_STATE_FILE, next, 384);
|
|
8121
|
+
return state.enforced;
|
|
8122
|
+
}
|
|
8123
|
+
|
|
7617
8124
|
// src/daemon/local-registry-upkeep.ts
|
|
7618
8125
|
function nowDate() {
|
|
7619
8126
|
return /* @__PURE__ */ new Date();
|
|
@@ -7622,33 +8129,47 @@ function mirrorConfigured(raw) {
|
|
|
7622
8129
|
const endpoints = mirrorEndpoints(parseRegistries(raw), PERSONAL_LOCAL_REGISTRY_HOST);
|
|
7623
8130
|
return endpoints.length === 1 && endpoints[0] === PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT;
|
|
7624
8131
|
}
|
|
7625
|
-
async function applyUnlessCollecting(ctx) {
|
|
8132
|
+
async function applyUnlessCollecting(ctx, auth) {
|
|
7626
8133
|
const deployment = await readRegistryDeployment(ctx.exec);
|
|
7627
8134
|
if (!deployment) throw new Error("the registry's Deployment could not be read");
|
|
7628
8135
|
if (gcMarkIsLive(deployment.mark, (ctx.now ?? nowDate)().getTime(), ctx.isAlive)) return false;
|
|
7629
8136
|
if (deployment.mark !== null && !await deleteSweepJob(ctx.exec)) {
|
|
7630
8137
|
throw new Error("a stopped cleanup's Job could not be deleted, so the registry stays stopped");
|
|
7631
8138
|
}
|
|
7632
|
-
await kubectlApply(ctx.exec, renderLocalRegistryManifests());
|
|
8139
|
+
await kubectlApply(ctx.exec, renderLocalRegistryManifests(auth));
|
|
7633
8140
|
if (deployment.mark !== null) await clearGcMark(ctx.exec);
|
|
7634
8141
|
return true;
|
|
7635
8142
|
}
|
|
7636
8143
|
async function deployLocalRegistry(ctx) {
|
|
7637
8144
|
await ctx.fs.ensureDir(LOCAL_REGISTRY_DATA_DIR, 448);
|
|
7638
|
-
const
|
|
8145
|
+
const credentials = await loadOrCreateRegistryCredentials(ctx.fs, ctx.random);
|
|
7639
8146
|
const raw = await ctx.fs.readFile(REGISTRIES_FILE);
|
|
7640
|
-
|
|
7641
|
-
const
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
8147
|
+
let file = parseRegistries(raw);
|
|
8148
|
+
const mirrorWritten = !mirrorConfigured(raw);
|
|
8149
|
+
const authWritten = !registryAuthConfigured(file, credentials);
|
|
8150
|
+
let backupPath = null;
|
|
8151
|
+
if (mirrorWritten) {
|
|
8152
|
+
file = upsertRegistryMirror(file, PERSONAL_LOCAL_REGISTRY_HOST, [
|
|
8153
|
+
PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT
|
|
8154
|
+
]);
|
|
8155
|
+
}
|
|
8156
|
+
if (authWritten) file = withLocalRegistryAuth(file, credentials);
|
|
8157
|
+
if (mirrorWritten || authWritten) {
|
|
8158
|
+
backupPath = await writeRegistriesWithBackup(ctx.fs, raw, file, (ctx.now ?? nowDate)());
|
|
8159
|
+
}
|
|
8160
|
+
const authEnforced = await settleRegistryAuth(ctx, credentials, authWritten);
|
|
8161
|
+
await kubectlApply(ctx.exec, renderLocalRegistrySecrets(credentials));
|
|
8162
|
+
const applied = await applyUnlessCollecting(ctx, authEnforced ? credentials : null);
|
|
8163
|
+
return { applied, mirrorWritten, authWritten, backupPath, authEnforced };
|
|
7646
8164
|
}
|
|
7647
8165
|
async function readMirrorState(fs) {
|
|
7648
8166
|
const raw = await fs.readFile(REGISTRIES_FILE);
|
|
7649
|
-
const
|
|
8167
|
+
const authPending = !await registryAuthEnforced((path) => fs.readFile(path));
|
|
8168
|
+
const fingerprint = createHash3("sha256").update(raw ?? "").digest("hex");
|
|
7650
8169
|
if (!mirrorConfigured(raw)) return { state: "absent", fingerprint };
|
|
7651
|
-
if (await mirrorLoaded((path) => fs.readFile(path)))
|
|
8170
|
+
if (!authPending && await mirrorLoaded((path) => fs.readFile(path))) {
|
|
8171
|
+
return { state: "loaded", fingerprint };
|
|
8172
|
+
}
|
|
7652
8173
|
const tried = (await fs.readFile(LOCAL_REGISTRY_MIRROR_RESTART_FILE))?.trim() === fingerprint;
|
|
7653
8174
|
return { state: tried ? "tried" : "owed", fingerprint };
|
|
7654
8175
|
}
|
|
@@ -7660,14 +8181,14 @@ async function loadRegistryMirror(ctx) {
|
|
|
7660
8181
|
if (state === "tried") {
|
|
7661
8182
|
return {
|
|
7662
8183
|
restarted: false,
|
|
7663
|
-
warning: `k3s restarted but did not load the local image registry's mirror, so pods cannot pull from that registry. Conveyor restarts k3s for it again only after /etc/rancher/k3s/registries.yaml changes. Check that k3s reads that file and keeps its data in the default directory: the mirror should appear under ${K3S_CERTS_D}.`
|
|
8184
|
+
warning: `k3s restarted but did not load the local image registry's mirror or credential, so pods cannot pull from that registry or the registry stays open to every account on this machine. Conveyor restarts k3s for it again only after /etc/rancher/k3s/registries.yaml changes. Check that k3s reads that file and keeps its data in the default directory: the mirror should appear under ${K3S_CERTS_D}.`
|
|
7664
8185
|
};
|
|
7665
8186
|
}
|
|
7666
8187
|
if (state !== "owed") return { restarted: false, warning: null };
|
|
7667
8188
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
7668
8189
|
return {
|
|
7669
8190
|
restarted: false,
|
|
7670
|
-
warning: `The local image registry needs a k3s restart to load its mirror, but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
8191
|
+
warning: `The local image registry needs a k3s restart to load its mirror or credential, but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
7671
8192
|
};
|
|
7672
8193
|
}
|
|
7673
8194
|
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
@@ -7849,7 +8370,18 @@ async function stdoutOf(exec, bin, args) {
|
|
|
7849
8370
|
const run = await exec(bin, args, { timeoutMs: 1e4 }).catch(() => null);
|
|
7850
8371
|
return run && run.exitCode === 0 ? run.stdout : null;
|
|
7851
8372
|
}
|
|
8373
|
+
async function dropInDirExists(exec) {
|
|
8374
|
+
const run = await exec("test", ["-d", K3S_CONFIG_DROPIN_DIR], { timeoutMs: 1e4 }).catch(
|
|
8375
|
+
() => null
|
|
8376
|
+
);
|
|
8377
|
+
if (run === null) return null;
|
|
8378
|
+
if (run.exitCode === 0) return true;
|
|
8379
|
+
return run.exitCode === 1 && run.stderr.trim() === "" ? false : null;
|
|
8380
|
+
}
|
|
7852
8381
|
async function readK3sConfigs(ctx) {
|
|
8382
|
+
const dirExists = await dropInDirExists(ctx.exec);
|
|
8383
|
+
if (dirExists === null) return null;
|
|
8384
|
+
if (!dirExists) return [await ctx.fs.readFile(K3S_OWNER_CONFIG)];
|
|
7853
8385
|
const listed = await stdoutOf(ctx.exec, "find", [
|
|
7854
8386
|
"-L",
|
|
7855
8387
|
K3S_CONFIG_DROPIN_DIR,
|
|
@@ -8106,6 +8638,55 @@ async function reconcileIsolation(ctx, desired, result) {
|
|
|
8106
8638
|
result.isolation = { applied };
|
|
8107
8639
|
}
|
|
8108
8640
|
|
|
8641
|
+
// src/lib/fs-port.ts
|
|
8642
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
8643
|
+
var nodeFileSystem = {
|
|
8644
|
+
async readFile(path) {
|
|
8645
|
+
try {
|
|
8646
|
+
return await readFile(path, "utf8");
|
|
8647
|
+
} catch {
|
|
8648
|
+
return null;
|
|
8649
|
+
}
|
|
8650
|
+
},
|
|
8651
|
+
async writeFile(path, content, mode) {
|
|
8652
|
+
await mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true });
|
|
8653
|
+
await writeFile(path, content, { encoding: "utf8", ...mode === void 0 ? {} : { mode } });
|
|
8654
|
+
if (mode !== void 0) await chmod(path, mode);
|
|
8655
|
+
},
|
|
8656
|
+
async removeFile(path) {
|
|
8657
|
+
await rm(path, { force: true });
|
|
8658
|
+
},
|
|
8659
|
+
async ensureDir(path, mode) {
|
|
8660
|
+
await mkdir(path, { recursive: true });
|
|
8661
|
+
if (mode !== void 0) await chmod(path, mode);
|
|
8662
|
+
}
|
|
8663
|
+
};
|
|
8664
|
+
function createMemoryFileSystem(seed = {}) {
|
|
8665
|
+
const files = new Map(Object.entries(seed));
|
|
8666
|
+
const modes = /* @__PURE__ */ new Map();
|
|
8667
|
+
const removed = [];
|
|
8668
|
+
return {
|
|
8669
|
+
files,
|
|
8670
|
+
modes,
|
|
8671
|
+
removed,
|
|
8672
|
+
readFile: (path) => Promise.resolve(files.get(path) ?? null),
|
|
8673
|
+
writeFile: (path, content, mode) => {
|
|
8674
|
+
files.set(path, content);
|
|
8675
|
+
if (mode !== void 0) modes.set(path, mode);
|
|
8676
|
+
return Promise.resolve();
|
|
8677
|
+
},
|
|
8678
|
+
removeFile: (path) => {
|
|
8679
|
+
files.delete(path);
|
|
8680
|
+
removed.push(path);
|
|
8681
|
+
return Promise.resolve();
|
|
8682
|
+
},
|
|
8683
|
+
ensureDir: (path, mode) => {
|
|
8684
|
+
if (mode !== void 0) modes.set(path, mode);
|
|
8685
|
+
return Promise.resolve();
|
|
8686
|
+
}
|
|
8687
|
+
};
|
|
8688
|
+
}
|
|
8689
|
+
|
|
8109
8690
|
// src/daemon/registry-tags.ts
|
|
8110
8691
|
var REPO_NAME = /^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
|
|
8111
8692
|
var TAG_NAME = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
|
|
@@ -8391,7 +8972,10 @@ async function collectRegistryGarbage(ports, options) {
|
|
|
8391
8972
|
blobsCollected: false
|
|
8392
8973
|
});
|
|
8393
8974
|
const now = ports.now ?? Date.now;
|
|
8394
|
-
const fetchImpl =
|
|
8975
|
+
const fetchImpl = withRegistryCredentials(
|
|
8976
|
+
ports.fetch ?? fetch,
|
|
8977
|
+
ports.readFile ?? ((path) => nodeFileSystem.readFile(path))
|
|
8978
|
+
);
|
|
8395
8979
|
const retain = await readRetainList(ports);
|
|
8396
8980
|
if (typeof retain === "string") return skip(retain);
|
|
8397
8981
|
const blocked = await registryBlocker(ports, now());
|
|
@@ -8499,7 +9083,7 @@ async function reportLocalImages(exec, inventory, wantedImages, cache) {
|
|
|
8499
9083
|
if (!inventory) return void 0;
|
|
8500
9084
|
const wanted = new Set(wantedImages);
|
|
8501
9085
|
const held = inventory.images.flatMap(
|
|
8502
|
-
(image) => (image.repoTags ?? []).filter((ref) => ref.startsWith("localhost/") && isImageRef(ref)).map((ref) => ({ ref, imageId: image.id }))
|
|
9086
|
+
(image) => (image.repoTags ?? []).filter((ref) => (ref.startsWith("localhost/") || wanted.has(ref)) && isImageRef(ref)).map((ref) => ({ ref, imageId: image.id }))
|
|
8503
9087
|
);
|
|
8504
9088
|
held.sort(
|
|
8505
9089
|
(a, b) => Number(wanted.has(b.ref)) - Number(wanted.has(a.ref)) || (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0)
|
|
@@ -8557,17 +9141,26 @@ var GRANTED_COMMAND_NAMES = LOCAL_BAKE_ROOT_COMMANDS.map((command) => command.na
|
|
|
8557
9141
|
|
|
8558
9142
|
// src/daemon/telemetry.ts
|
|
8559
9143
|
var LOCAL_REGISTRY_WARN_AFTER_MS = 10 * 6e4;
|
|
9144
|
+
var LOCAL_REGISTRY_OPEN_WARNING = "The local image registry is open to every account on this machine: any of them can read and replace the images your workspaces start from. It requires its credential once k3s restarts and loads it.";
|
|
8560
9145
|
async function reportLocalRegistry(ports, watch, now = Date.now()) {
|
|
8561
9146
|
if (!watch) return { fields: {}, warnings: [] };
|
|
8562
|
-
const ready = await
|
|
9147
|
+
const { ready, authEnforced } = await localRegistryStatus({
|
|
9148
|
+
readFile: ports.readFile,
|
|
9149
|
+
fetch: ports.fetch
|
|
9150
|
+
});
|
|
8563
9151
|
const paused = (watch.pausedUntil ?? 0) > now;
|
|
8564
9152
|
watch.notReadySince = ready || paused ? null : watch.notReadySince ?? now;
|
|
8565
9153
|
const stuckMs = watch.notReadySince === null ? 0 : now - watch.notReadySince;
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
warnings
|
|
9154
|
+
const warnings = [];
|
|
9155
|
+
if (stuckMs >= LOCAL_REGISTRY_WARN_AFTER_MS) {
|
|
9156
|
+
warnings.push(
|
|
8569
9157
|
`The local image registry has not been ready for ${Math.floor(stuckMs / 6e4)} minutes, so shadow bakes cannot use it yet. Check its pod with \`k3s kubectl -n conveyor-system get pods -l app.kubernetes.io/name=conveyor-registry\`. If the pod is running, k3s has not loaded the registry's mirror yet: it restarts k3s for that only while no Conveyor pod runs.`
|
|
8570
|
-
|
|
9158
|
+
);
|
|
9159
|
+
}
|
|
9160
|
+
if (!authEnforced) warnings.push(LOCAL_REGISTRY_OPEN_WARNING);
|
|
9161
|
+
return {
|
|
9162
|
+
fields: { localRegistry: { host: PERSONAL_LOCAL_REGISTRY_HOST, ready, authEnforced } },
|
|
9163
|
+
warnings
|
|
8571
9164
|
};
|
|
8572
9165
|
}
|
|
8573
9166
|
var VM_CAPACITY_FILE = "/etc/conveyor-vm/capacity.json";
|
|
@@ -8690,6 +9283,7 @@ async function collectHeartbeat(ports, state, inventory) {
|
|
|
8690
9283
|
...localImages ? { localImages } : {},
|
|
8691
9284
|
...registry.fields,
|
|
8692
9285
|
...state.isolation ? { isolation: state.isolation } : {},
|
|
9286
|
+
...state.cpuQuotaApplied === void 0 ? {} : { cpuQuotaApplied: state.cpuQuotaApplied },
|
|
8693
9287
|
localWarnings: withinHeartbeatBounds([
|
|
8694
9288
|
...state.localWarnings,
|
|
8695
9289
|
...gcWarning ? [gcWarning] : [],
|
|
@@ -8740,7 +9334,15 @@ async function collect(ports, now) {
|
|
|
8740
9334
|
if (watch) watch.pausedUntil = now.getTime() + REGISTRY_GC_MARK_TTL_MS;
|
|
8741
9335
|
try {
|
|
8742
9336
|
const outcome = await collectRegistryGarbage(
|
|
8743
|
-
{
|
|
9337
|
+
{
|
|
9338
|
+
exec,
|
|
9339
|
+
log,
|
|
9340
|
+
requestDesiredState,
|
|
9341
|
+
fetch: fetch2,
|
|
9342
|
+
readFile: (path) => ports.fs.readFile(path),
|
|
9343
|
+
sleep: sleep2,
|
|
9344
|
+
isAlive
|
|
9345
|
+
},
|
|
8744
9346
|
{ dryRun: false }
|
|
8745
9347
|
);
|
|
8746
9348
|
if (outcome.skipped) return outcome.skipped;
|
|
@@ -8786,48 +9388,7 @@ async function runRegistryUpkeep(ports, lastGcAt, maintenanceHour) {
|
|
|
8786
9388
|
}
|
|
8787
9389
|
|
|
8788
9390
|
// src/daemon/reconcile.ts
|
|
8789
|
-
function
|
|
8790
|
-
return `${change} Conveyor did not install this server's k3s, so it never restarts it: run \`sudo systemctl restart k3s\` when no build runs.`;
|
|
8791
|
-
}
|
|
8792
|
-
async function reconcileKubelet(ctx, desired, result, managed) {
|
|
8793
|
-
const ownerConfig = await ctx.fs.readFile(K3S_OWNER_CONFIG);
|
|
8794
|
-
const rendered = renderK3sDropIn(desired.kubelet, ownerConfig, { disableComponents: managed });
|
|
8795
|
-
result.skippedKubeletFlags = rendered.skipped;
|
|
8796
|
-
result.effectiveImageGcHighThreshold = rendered.skipped.includes("image-gc-high-threshold") ? ownerImageGcHighThreshold(ownerConfig) : desired.kubelet.imageGcHighThreshold;
|
|
8797
|
-
for (const flag of rendered.skipped) {
|
|
8798
|
-
result.warnings.push(
|
|
8799
|
-
`Left \`${flag}\` to your own /etc/rancher/k3s/config.yaml \u2014 Conveyor does not override a setting you chose.`
|
|
8800
|
-
);
|
|
8801
|
-
}
|
|
8802
|
-
const current = await ctx.fs.readFile(K3S_CONFIG_DROPIN);
|
|
8803
|
-
if (rendered.content === null) {
|
|
8804
|
-
if (current !== null) await ctx.fs.removeFile(K3S_CONFIG_DROPIN);
|
|
8805
|
-
return;
|
|
8806
|
-
}
|
|
8807
|
-
if (current === rendered.content) return;
|
|
8808
|
-
await ctx.fs.ensureDir(K3S_CONFIG_DROPIN.slice(0, K3S_CONFIG_DROPIN.lastIndexOf("/")));
|
|
8809
|
-
await ctx.fs.writeFile(K3S_CONFIG_DROPIN, rendered.content, 420);
|
|
8810
|
-
if (!managed) {
|
|
8811
|
-
result.warnings.push(ownerRestartWarning("Kubelet settings changed."));
|
|
8812
|
-
return;
|
|
8813
|
-
}
|
|
8814
|
-
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
8815
|
-
result.warnings.push(
|
|
8816
|
-
`Kubelet settings changed but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
8817
|
-
);
|
|
8818
|
-
return;
|
|
8819
|
-
}
|
|
8820
|
-
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
8821
|
-
if (restart.exitCode === 0) {
|
|
8822
|
-
result.restartedK3s = true;
|
|
8823
|
-
} else {
|
|
8824
|
-
result.warnings.push(
|
|
8825
|
-
`Could not restart k3s to apply new kubelet settings: ${restart.stderr.trim().slice(0, 200)}`
|
|
8826
|
-
);
|
|
8827
|
-
}
|
|
8828
|
-
}
|
|
8829
|
-
async function reconcilePriorityClass(ctx, desired, result) {
|
|
8830
|
-
const name = assertK8sName(desired.priorityClass.name, "priorityClass.name");
|
|
9391
|
+
async function livePriorityClassValue(ctx, name) {
|
|
8831
9392
|
const read = await ctx.exec("k3s", [
|
|
8832
9393
|
"kubectl",
|
|
8833
9394
|
"get",
|
|
@@ -8836,30 +9397,59 @@ async function reconcilePriorityClass(ctx, desired, result) {
|
|
|
8836
9397
|
"-o",
|
|
8837
9398
|
"jsonpath={.value}"
|
|
8838
9399
|
]);
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
9400
|
+
const value = Number.parseInt(read.stdout.trim(), 10);
|
|
9401
|
+
return read.exitCode === 0 && Number.isFinite(value) ? value : null;
|
|
9402
|
+
}
|
|
9403
|
+
async function reconcilePriorityClass(ctx, desired, result) {
|
|
9404
|
+
const name = assertK8sName(desired.priorityClass.name, "priorityClass.name");
|
|
9405
|
+
const wanted = desired.priorityClass.value;
|
|
9406
|
+
const existingValue = await livePriorityClassValue(ctx, name);
|
|
9407
|
+
if (existingValue === null || existingValue === wanted) return "ready";
|
|
9408
|
+
if (!declaresPriorityClass(desired.rbacManifest, name, wanted)) {
|
|
9409
|
+
result.warnings.push(
|
|
9410
|
+
`The \`${name}\` PriorityClass has value ${existingValue}, but Conveyor's manifest does not declare it at ${wanted}, so it is left as it is. This should never happen; report it.`
|
|
9411
|
+
);
|
|
9412
|
+
return "kept";
|
|
9413
|
+
}
|
|
8842
9414
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
8843
9415
|
result.warnings.push(
|
|
8844
|
-
`The \`${name}\` PriorityClass has value ${existingValue}, but Conveyor needs ${
|
|
9416
|
+
`The \`${name}\` PriorityClass has value ${existingValue}, but Conveyor needs ${wanted}. The value is immutable, so it must be recreated \u2014 deferred because ${busyReason(ctx.runningConveyorPods)}.`
|
|
8845
9417
|
);
|
|
8846
|
-
return;
|
|
9418
|
+
return "kept";
|
|
8847
9419
|
}
|
|
8848
|
-
const removed = await ctx.exec("k3s", [
|
|
9420
|
+
const removed = await ctx.exec("k3s", [
|
|
9421
|
+
"kubectl",
|
|
9422
|
+
"delete",
|
|
9423
|
+
"priorityclass",
|
|
9424
|
+
name,
|
|
9425
|
+
"--ignore-not-found"
|
|
9426
|
+
]);
|
|
8849
9427
|
if (removed.exitCode !== 0) {
|
|
8850
9428
|
result.warnings.push(
|
|
8851
|
-
`Could not replace the \`${name}\` PriorityClass (value ${existingValue}, needs ${
|
|
9429
|
+
`Could not replace the \`${name}\` PriorityClass (value ${existingValue}, needs ${wanted}): ${removed.stderr.trim().slice(0, 160)}`
|
|
8852
9430
|
);
|
|
8853
|
-
return;
|
|
9431
|
+
return "kept";
|
|
8854
9432
|
}
|
|
9433
|
+
return "removed";
|
|
9434
|
+
}
|
|
9435
|
+
async function applyRbac(ctx, desired, result) {
|
|
9436
|
+
const outcome = await reconcilePriorityClass(ctx, desired, result);
|
|
9437
|
+
const manifest = outcome === "kept" ? withoutKind(desired.rbacManifest, "PriorityClass") : desired.rbacManifest;
|
|
8855
9438
|
try {
|
|
8856
|
-
await kubectlApply(ctx.exec,
|
|
8857
|
-
result.recreatedPriorityClass = true;
|
|
9439
|
+
await kubectlApply(ctx.exec, manifest);
|
|
8858
9440
|
} catch (error) {
|
|
9441
|
+
if (outcome !== "removed") throw error;
|
|
9442
|
+
const value = await livePriorityClassValue(ctx, desired.priorityClass.name).catch(() => null);
|
|
9443
|
+
if (value === desired.priorityClass.value) {
|
|
9444
|
+
result.recreatedPriorityClass = true;
|
|
9445
|
+
throw error;
|
|
9446
|
+
}
|
|
8859
9447
|
result.warnings.push(
|
|
8860
|
-
`The \`${name}\` PriorityClass was removed to change its value but could not be recreated: ${error instanceof Error ? error.message : String(error)}. No build can start on this machine until it exists \u2014 run \`conveyor-k3 sync\`.`
|
|
9448
|
+
`The \`${desired.priorityClass.name}\` PriorityClass was removed to change its value but could not be recreated: ${error instanceof Error ? error.message : String(error)}. No build can start on this machine until it exists \u2014 run \`conveyor-k3 sync\`.`
|
|
8861
9449
|
);
|
|
9450
|
+
return;
|
|
8862
9451
|
}
|
|
9452
|
+
if (outcome === "removed") result.recreatedPriorityClass = true;
|
|
8863
9453
|
}
|
|
8864
9454
|
async function step(result, label, run) {
|
|
8865
9455
|
try {
|
|
@@ -8885,12 +9475,7 @@ async function reconcile(ctx, desired) {
|
|
|
8885
9475
|
`Conveyor sent a cluster manifest containing disallowed kind(s) ${disallowedKinds.join(", ")} \u2014 refused. RBAC and PriorityClass steps skipped; this should never happen, report it.`
|
|
8886
9476
|
);
|
|
8887
9477
|
} else {
|
|
8888
|
-
await step(result, "Could not apply RBAC", () =>
|
|
8889
|
-
await step(
|
|
8890
|
-
result,
|
|
8891
|
-
"Could not reconcile the PriorityClass",
|
|
8892
|
-
() => reconcilePriorityClass(ctx, desired, result)
|
|
8893
|
-
);
|
|
9478
|
+
await step(result, "Could not apply RBAC", () => applyRbac(ctx, desired, result));
|
|
8894
9479
|
}
|
|
8895
9480
|
await step(
|
|
8896
9481
|
result,
|
|
@@ -8922,11 +9507,23 @@ async function reconcile(ctx, desired) {
|
|
|
8922
9507
|
"Could not apply kubelet settings",
|
|
8923
9508
|
() => reconcileKubelet(ctx, desired, result, managed)
|
|
8924
9509
|
);
|
|
9510
|
+
if (desired.isolation !== void 0) {
|
|
9511
|
+
await step(result, "Could not cap pod CPU", async () => {
|
|
9512
|
+
result.cpuQuotaApplied = await reconcilePodCpuQuota(ctx.exec, desired, result.warnings);
|
|
9513
|
+
});
|
|
9514
|
+
if (desired.kubelet.systemReserved?.cpu && result.cpuQuotaApplied === void 0) {
|
|
9515
|
+
result.cpuQuotaApplied = false;
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
8925
9518
|
if (registry && !result.restartedK3s) {
|
|
8926
9519
|
await step(result, "Could not load the local image registry mirror", async () => {
|
|
8927
9520
|
if (!managed) {
|
|
8928
9521
|
if (await registryMirrorPending(ctx.fs))
|
|
8929
|
-
result.warnings.push(
|
|
9522
|
+
result.warnings.push(
|
|
9523
|
+
ownerRestartWarning(
|
|
9524
|
+
"The local image registry's mirror or credential is new, and the registry stays open to every account on this machine until k3s loads it."
|
|
9525
|
+
)
|
|
9526
|
+
);
|
|
8930
9527
|
return;
|
|
8931
9528
|
}
|
|
8932
9529
|
const loaded = await loadRegistryMirror(ctx);
|
|
@@ -8989,6 +9586,8 @@ var ClusterDaemon = class {
|
|
|
8989
9586
|
* against k3s's own settings before reporting it.
|
|
8990
9587
|
*/
|
|
8991
9588
|
isolation = void 0;
|
|
9589
|
+
/** Whether the pod slice's CPU cap is in force, from the last reconcile. */
|
|
9590
|
+
cpuQuotaApplied = void 0;
|
|
8992
9591
|
timers = [];
|
|
8993
9592
|
/** When the last store walk started, so one that failed is not retried on every reconcile. */
|
|
8994
9593
|
storeWalkStartedAt = 0;
|
|
@@ -9071,6 +9670,7 @@ var ClusterDaemon = class {
|
|
|
9071
9670
|
);
|
|
9072
9671
|
this.warnings = result.warnings;
|
|
9073
9672
|
this.isolation = result.isolation;
|
|
9673
|
+
this.cpuQuotaApplied = result.cpuQuotaApplied;
|
|
9074
9674
|
this.scheduleIsolationRetry();
|
|
9075
9675
|
for (const warning of result.warnings) this.ports.log(warning);
|
|
9076
9676
|
if (result.prunedNamespaces.length > 0) {
|
|
@@ -9227,6 +9827,11 @@ var ClusterDaemon = class {
|
|
|
9227
9827
|
);
|
|
9228
9828
|
}
|
|
9229
9829
|
for (const failure of result.failures) this.ports.log(`Image prune failed \u2014 ${failure}`);
|
|
9830
|
+
if (result.pending.length > 0) {
|
|
9831
|
+
this.ports.log(
|
|
9832
|
+
`Image removal still in progress for ${result.pending.length} image(s); the next prune re-checks them.`
|
|
9833
|
+
);
|
|
9834
|
+
}
|
|
9230
9835
|
} catch (error) {
|
|
9231
9836
|
this.ports.log(
|
|
9232
9837
|
`Skipping image prune: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -9265,6 +9870,7 @@ var ClusterDaemon = class {
|
|
|
9265
9870
|
localImageHashes: this.localImageHashes,
|
|
9266
9871
|
...registryOn ? { localRegistry: this.registryWatch } : {},
|
|
9267
9872
|
...isolation.isolation ? { isolation: isolation.isolation } : {},
|
|
9873
|
+
cpuQuotaApplied: this.cpuQuotaApplied,
|
|
9268
9874
|
k3sStoreBytes: this.measuredStoreBytes
|
|
9269
9875
|
},
|
|
9270
9876
|
inventory
|
|
@@ -9399,55 +10005,6 @@ function renderAllUnits(options) {
|
|
|
9399
10005
|
};
|
|
9400
10006
|
}
|
|
9401
10007
|
|
|
9402
|
-
// src/lib/fs-port.ts
|
|
9403
|
-
import { chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
9404
|
-
var nodeFileSystem = {
|
|
9405
|
-
async readFile(path) {
|
|
9406
|
-
try {
|
|
9407
|
-
return await readFile(path, "utf8");
|
|
9408
|
-
} catch {
|
|
9409
|
-
return null;
|
|
9410
|
-
}
|
|
9411
|
-
},
|
|
9412
|
-
async writeFile(path, content, mode) {
|
|
9413
|
-
await mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true });
|
|
9414
|
-
await writeFile(path, content, "utf8");
|
|
9415
|
-
if (mode !== void 0) await chmod(path, mode);
|
|
9416
|
-
},
|
|
9417
|
-
async removeFile(path) {
|
|
9418
|
-
await rm(path, { force: true });
|
|
9419
|
-
},
|
|
9420
|
-
async ensureDir(path, mode) {
|
|
9421
|
-
await mkdir(path, { recursive: true });
|
|
9422
|
-
if (mode !== void 0) await chmod(path, mode);
|
|
9423
|
-
}
|
|
9424
|
-
};
|
|
9425
|
-
function createMemoryFileSystem(seed = {}) {
|
|
9426
|
-
const files = new Map(Object.entries(seed));
|
|
9427
|
-
const modes = /* @__PURE__ */ new Map();
|
|
9428
|
-
const removed = [];
|
|
9429
|
-
return {
|
|
9430
|
-
files,
|
|
9431
|
-
modes,
|
|
9432
|
-
removed,
|
|
9433
|
-
readFile: (path) => Promise.resolve(files.get(path) ?? null),
|
|
9434
|
-
writeFile: (path, content, mode) => {
|
|
9435
|
-
files.set(path, content);
|
|
9436
|
-
if (mode !== void 0) modes.set(path, mode);
|
|
9437
|
-
return Promise.resolve();
|
|
9438
|
-
},
|
|
9439
|
-
removeFile: (path) => {
|
|
9440
|
-
files.delete(path);
|
|
9441
|
-
removed.push(path);
|
|
9442
|
-
return Promise.resolve();
|
|
9443
|
-
},
|
|
9444
|
-
ensureDir: (path, mode) => {
|
|
9445
|
-
if (mode !== void 0) modes.set(path, mode);
|
|
9446
|
-
return Promise.resolve();
|
|
9447
|
-
}
|
|
9448
|
-
};
|
|
9449
|
-
}
|
|
9450
|
-
|
|
9451
10008
|
// src/verbs.ts
|
|
9452
10009
|
var VERBS = [
|
|
9453
10010
|
"vm",
|
|
@@ -9613,17 +10170,36 @@ async function removeLocalRegistry(ports) {
|
|
|
9613
10170
|
`Could not remove the local image registry from the cluster: ${removed.stderr.trim().slice(0, 200)}`
|
|
9614
10171
|
);
|
|
9615
10172
|
}
|
|
10173
|
+
for (const [namespace, name] of [
|
|
10174
|
+
[PERSONAL_LOCAL_REGISTRY_NAMESPACE, LOCAL_REGISTRY_HTPASSWD_SECRET],
|
|
10175
|
+
[PERSONAL_BAKE_NAMESPACE, PERSONAL_LOCAL_REGISTRY_PUSH_SECRET]
|
|
10176
|
+
]) {
|
|
10177
|
+
const secret = await ports.exec(
|
|
10178
|
+
"k3s",
|
|
10179
|
+
["kubectl", "-n", namespace, "delete", "secret", name, "--ignore-not-found"],
|
|
10180
|
+
{ timeoutMs: 6e4 }
|
|
10181
|
+
);
|
|
10182
|
+
if (secret.exitCode !== 0) {
|
|
10183
|
+
ports.log(
|
|
10184
|
+
`Could not remove the local image registry's Secret ${namespace}/${name}: ${secret.stderr.trim().slice(0, 200)}`
|
|
10185
|
+
);
|
|
10186
|
+
}
|
|
10187
|
+
}
|
|
9616
10188
|
const raw = await ports.fs.readFile(REGISTRIES_FILE);
|
|
9617
10189
|
const parsed = parseRegistries(raw);
|
|
9618
|
-
|
|
10190
|
+
const next = LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
10191
|
+
removeRegistryAuth,
|
|
10192
|
+
removeRegistryMirror(parsed, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
10193
|
+
);
|
|
10194
|
+
if (serializeRegistries(next) === serializeRegistries(parsed)) return;
|
|
9619
10195
|
const backup = await writeRegistriesWithBackup(
|
|
9620
10196
|
ports.fs,
|
|
9621
10197
|
raw,
|
|
9622
|
-
|
|
10198
|
+
next,
|
|
9623
10199
|
(ports.now ?? (() => /* @__PURE__ */ new Date()))()
|
|
9624
10200
|
);
|
|
9625
10201
|
ports.log(
|
|
9626
|
-
`Removed the local image registry's mirror from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
10202
|
+
`Removed the local image registry's mirror and credential from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
9627
10203
|
);
|
|
9628
10204
|
}
|
|
9629
10205
|
async function removeIsolation(ports) {
|
|
@@ -9680,6 +10256,10 @@ async function uninstall(ports, options = {}) {
|
|
|
9680
10256
|
}
|
|
9681
10257
|
await removeLocalRegistry(ports);
|
|
9682
10258
|
await removeIsolation(ports);
|
|
10259
|
+
const lifted = await liftPodCpuQuota(ports.exec).catch(
|
|
10260
|
+
(error) => error instanceof Error ? error.message : String(error)
|
|
10261
|
+
);
|
|
10262
|
+
if (lifted) ports.log(`Could not lift Conveyor's CPU cap on kubepods.slice: ${lifted}`);
|
|
9683
10263
|
for (const path of OWNED_FILES) {
|
|
9684
10264
|
await ports.fs.removeFile(path);
|
|
9685
10265
|
result.removedFiles.push(path);
|
|
@@ -9732,6 +10312,45 @@ async function uninstall(ports, options = {}) {
|
|
|
9732
10312
|
return result;
|
|
9733
10313
|
}
|
|
9734
10314
|
|
|
10315
|
+
// src/lib/bins.ts
|
|
10316
|
+
var INSTALLER_PATHS = {
|
|
10317
|
+
bun: "/root/.bun/bin/bun",
|
|
10318
|
+
cli: "/root/.bun/bin/conveyor-k3",
|
|
10319
|
+
cloudflared: "/usr/local/bin/cloudflared"
|
|
10320
|
+
};
|
|
10321
|
+
var CLI_PATH_LINK = "/usr/local/bin/conveyor-k3";
|
|
10322
|
+
async function linkCliOnPath(exec, target = INSTALLER_PATHS.cli) {
|
|
10323
|
+
if (target === CLI_PATH_LINK) return { linked: false };
|
|
10324
|
+
const present = await exec("test", ["-x", target]).catch(() => null);
|
|
10325
|
+
if (present?.exitCode !== 0) return { linked: false };
|
|
10326
|
+
const link = await exec("ln", ["-sfn", target, CLI_PATH_LINK]).catch(() => null);
|
|
10327
|
+
if (link?.exitCode === 0) return { linked: true };
|
|
10328
|
+
return {
|
|
10329
|
+
linked: false,
|
|
10330
|
+
problem: `Could not link ${CLI_PATH_LINK} to ${target}: ${link?.stderr.trim().slice(0, 200) || "ln failed"}`
|
|
10331
|
+
};
|
|
10332
|
+
}
|
|
10333
|
+
|
|
10334
|
+
// src/lib/cloudflared.ts
|
|
10335
|
+
var PACKAGE_MANAGER_REFUSAL = "installed by a package manager";
|
|
10336
|
+
async function resolveRealPath(exec, bin) {
|
|
10337
|
+
const result = await exec("readlink", ["-f", bin]).catch(() => null);
|
|
10338
|
+
const resolved = result?.exitCode === 0 ? result.stdout.trim() : "";
|
|
10339
|
+
return resolved.startsWith("/") ? resolved : bin;
|
|
10340
|
+
}
|
|
10341
|
+
async function cloudflaredUpdater(exec, bin) {
|
|
10342
|
+
const real = await resolveRealPath(exec, bin);
|
|
10343
|
+
const owner = await exec("dpkg", ["-S", real]).catch(() => null);
|
|
10344
|
+
return owner?.exitCode === 0 ? "package-manager" : "conveyor";
|
|
10345
|
+
}
|
|
10346
|
+
function isPackageManagerRefusal(output) {
|
|
10347
|
+
return `${output.stdout}
|
|
10348
|
+
${output.stderr}`.toLowerCase().includes(PACKAGE_MANAGER_REFUSAL);
|
|
10349
|
+
}
|
|
10350
|
+
function describeCloudflaredUpdater(updater) {
|
|
10351
|
+
return updater === "package-manager" ? "cloudflared is updated by your package manager, not by Conveyor" : "cloudflared is updated by Conveyor's daily update";
|
|
10352
|
+
}
|
|
10353
|
+
|
|
9735
10354
|
// src/commands/update.ts
|
|
9736
10355
|
var CLOUDFLARED_UPDATED_EXIT = 11;
|
|
9737
10356
|
function readExecStart(unit) {
|
|
@@ -9819,7 +10438,22 @@ async function updatePackage(ports, result, targetVersion) {
|
|
|
9819
10438
|
await ports.exec("systemctl", ["restart", DAEMON_UNIT]);
|
|
9820
10439
|
}
|
|
9821
10440
|
async function updateCloudflared(ports, result) {
|
|
10441
|
+
const packaged = () => {
|
|
10442
|
+
result.cloudflaredUpdater = "package-manager";
|
|
10443
|
+
result.notes.push(
|
|
10444
|
+
`cloudflared update skipped: ${describeCloudflaredUpdater("package-manager")}`
|
|
10445
|
+
);
|
|
10446
|
+
};
|
|
10447
|
+
if (await cloudflaredUpdater(ports.exec, ports.bins.cloudflared) === "package-manager") {
|
|
10448
|
+
packaged();
|
|
10449
|
+
return;
|
|
10450
|
+
}
|
|
9822
10451
|
const update = await ports.exec(ports.bins.cloudflared, ["update"], { timeoutMs: 5 * 6e4 });
|
|
10452
|
+
if (isPackageManagerRefusal(update)) {
|
|
10453
|
+
packaged();
|
|
10454
|
+
return;
|
|
10455
|
+
}
|
|
10456
|
+
result.cloudflaredUpdater = "conveyor";
|
|
9823
10457
|
if (update.exitCode !== CLOUDFLARED_UPDATED_EXIT) return;
|
|
9824
10458
|
result.cloudflaredUpdated = true;
|
|
9825
10459
|
ports.log("cloudflared was updated; restarting the tunnel.");
|
|
@@ -9829,10 +10463,13 @@ async function runUpdate(ports, decision, channel, targetVersion) {
|
|
|
9829
10463
|
const result = {
|
|
9830
10464
|
packageUpdated: false,
|
|
9831
10465
|
cloudflaredUpdated: false,
|
|
10466
|
+
cloudflaredUpdater: null,
|
|
9832
10467
|
k3sUpgraded: false,
|
|
9833
10468
|
notes: []
|
|
9834
10469
|
};
|
|
9835
10470
|
await updatePackage(ports, result, targetVersion);
|
|
10471
|
+
const linked = await linkCliOnPath(ports.exec);
|
|
10472
|
+
if (linked.problem) result.notes.push(linked.problem);
|
|
9836
10473
|
await updateCloudflared(ports, result);
|
|
9837
10474
|
if (!decision.upgrade) {
|
|
9838
10475
|
result.notes.push(`k3s upgrade skipped: ${decision.reason}`);
|
|
@@ -9865,6 +10502,9 @@ async function runUpdate(ports, decision, channel, targetVersion) {
|
|
|
9865
10502
|
return result;
|
|
9866
10503
|
}
|
|
9867
10504
|
|
|
10505
|
+
// src/lib/env.ts
|
|
10506
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
10507
|
+
|
|
9868
10508
|
// src/commands/setup.ts
|
|
9869
10509
|
var K3S_MIN_MAJOR_MINOR = [1, 29];
|
|
9870
10510
|
function parseK3sVersion(output) {
|
|
@@ -10761,22 +11401,6 @@ function rewriteQemuArgs(args, network, ncPath = "/usr/bin/nc", hostContext = {}
|
|
|
10761
11401
|
return output;
|
|
10762
11402
|
}
|
|
10763
11403
|
|
|
10764
|
-
// src/vm/memory-budget.ts
|
|
10765
|
-
var RELAY_RSS_ALLOWANCE_MIB = 128;
|
|
10766
|
-
var BASE_RELAY_SLOTS = 16;
|
|
10767
|
-
function relayAllowanceGiB(relaySlots = BASE_RELAY_SLOTS) {
|
|
10768
|
-
return Math.max(0, relaySlots - BASE_RELAY_SLOTS) * RELAY_RSS_ALLOWANCE_MIB / 1024;
|
|
10769
|
-
}
|
|
10770
|
-
function maximumGuestMemoryGiBWithin(budgetGiB, relaySlots = BASE_RELAY_SLOTS) {
|
|
10771
|
-
const guestBudget = budgetGiB - relayAllowanceGiB(relaySlots);
|
|
10772
|
-
if (!Number.isFinite(guestBudget) || guestBudget <= 0) return 0;
|
|
10773
|
-
return Math.max(0, Math.floor(Math.min(guestBudget - 1, guestBudget / 1.25)));
|
|
10774
|
-
}
|
|
10775
|
-
function maximumGuestMemoryGiB(hostBytes, relaySlots = BASE_RELAY_SLOTS) {
|
|
10776
|
-
if (!Number.isFinite(hostBytes) || hostBytes <= 0) return 0;
|
|
10777
|
-
return maximumGuestMemoryGiBWithin(hostBytes / 1024 ** 3 * 0.75, relaySlots);
|
|
10778
|
-
}
|
|
10779
|
-
|
|
10780
11404
|
// src/vm/host-state.ts
|
|
10781
11405
|
import { access, chmod as chmod2, lstat, mkdir as mkdir2, readFile as readFile2, rename, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
10782
11406
|
import { homedir, uptime } from "os";
|
|
@@ -10868,7 +11492,6 @@ export {
|
|
|
10868
11492
|
renderAllUnits,
|
|
10869
11493
|
renderGuestDnsProvision,
|
|
10870
11494
|
renderK3sDropIn,
|
|
10871
|
-
renderKubeletDropIn,
|
|
10872
11495
|
renderRegistryAuthSummary,
|
|
10873
11496
|
resolveCloudflaredSrvRecords,
|
|
10874
11497
|
resolveNetwork,
|