@rallycry/conveyor-k3 0.1.12 → 0.1.14
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 +2720 -1291
- package/dist/index.d.ts +85 -28
- package/dist/index.js +1533 -423
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -101,7 +101,7 @@ function createClusterProof(apiUrl, clusterToken, key) {
|
|
|
101
101
|
const origin = new URL(apiUrl).origin;
|
|
102
102
|
const timestamp = Date.now();
|
|
103
103
|
const nonce = randomBytes(32).toString("base64url");
|
|
104
|
-
const
|
|
104
|
+
const message2 = `conveyor-device-v1
|
|
105
105
|
${origin}
|
|
106
106
|
${clusterToken}
|
|
107
107
|
${timestamp}
|
|
@@ -109,7 +109,7 @@ ${nonce}`;
|
|
|
109
109
|
return {
|
|
110
110
|
timestamp,
|
|
111
111
|
nonce,
|
|
112
|
-
signature: sign(null, Buffer.from(
|
|
112
|
+
signature: sign(null, Buffer.from(message2, "utf8"), key).toString("base64url")
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
function deviceClusterAuth(apiUrl, clusterToken, keyPath = DEVICE_KEY_PATH, ownerUid = 0) {
|
|
@@ -487,104 +487,104 @@ ZodError.create = (issues) => {
|
|
|
487
487
|
|
|
488
488
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
489
489
|
var errorMap = (issue, _ctx) => {
|
|
490
|
-
let
|
|
490
|
+
let message2;
|
|
491
491
|
switch (issue.code) {
|
|
492
492
|
case ZodIssueCode.invalid_type:
|
|
493
493
|
if (issue.received === ZodParsedType.undefined) {
|
|
494
|
-
|
|
494
|
+
message2 = "Required";
|
|
495
495
|
} else {
|
|
496
|
-
|
|
496
|
+
message2 = `Expected ${issue.expected}, received ${issue.received}`;
|
|
497
497
|
}
|
|
498
498
|
break;
|
|
499
499
|
case ZodIssueCode.invalid_literal:
|
|
500
|
-
|
|
500
|
+
message2 = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`;
|
|
501
501
|
break;
|
|
502
502
|
case ZodIssueCode.unrecognized_keys:
|
|
503
|
-
|
|
503
|
+
message2 = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`;
|
|
504
504
|
break;
|
|
505
505
|
case ZodIssueCode.invalid_union:
|
|
506
|
-
|
|
506
|
+
message2 = `Invalid input`;
|
|
507
507
|
break;
|
|
508
508
|
case ZodIssueCode.invalid_union_discriminator:
|
|
509
|
-
|
|
509
|
+
message2 = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`;
|
|
510
510
|
break;
|
|
511
511
|
case ZodIssueCode.invalid_enum_value:
|
|
512
|
-
|
|
512
|
+
message2 = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`;
|
|
513
513
|
break;
|
|
514
514
|
case ZodIssueCode.invalid_arguments:
|
|
515
|
-
|
|
515
|
+
message2 = `Invalid function arguments`;
|
|
516
516
|
break;
|
|
517
517
|
case ZodIssueCode.invalid_return_type:
|
|
518
|
-
|
|
518
|
+
message2 = `Invalid function return type`;
|
|
519
519
|
break;
|
|
520
520
|
case ZodIssueCode.invalid_date:
|
|
521
|
-
|
|
521
|
+
message2 = `Invalid date`;
|
|
522
522
|
break;
|
|
523
523
|
case ZodIssueCode.invalid_string:
|
|
524
524
|
if (typeof issue.validation === "object") {
|
|
525
525
|
if ("includes" in issue.validation) {
|
|
526
|
-
|
|
526
|
+
message2 = `Invalid input: must include "${issue.validation.includes}"`;
|
|
527
527
|
if (typeof issue.validation.position === "number") {
|
|
528
|
-
|
|
528
|
+
message2 = `${message2} at one or more positions greater than or equal to ${issue.validation.position}`;
|
|
529
529
|
}
|
|
530
530
|
} else if ("startsWith" in issue.validation) {
|
|
531
|
-
|
|
531
|
+
message2 = `Invalid input: must start with "${issue.validation.startsWith}"`;
|
|
532
532
|
} else if ("endsWith" in issue.validation) {
|
|
533
|
-
|
|
533
|
+
message2 = `Invalid input: must end with "${issue.validation.endsWith}"`;
|
|
534
534
|
} else {
|
|
535
535
|
util.assertNever(issue.validation);
|
|
536
536
|
}
|
|
537
537
|
} else if (issue.validation !== "regex") {
|
|
538
|
-
|
|
538
|
+
message2 = `Invalid ${issue.validation}`;
|
|
539
539
|
} else {
|
|
540
|
-
|
|
540
|
+
message2 = "Invalid";
|
|
541
541
|
}
|
|
542
542
|
break;
|
|
543
543
|
case ZodIssueCode.too_small:
|
|
544
544
|
if (issue.type === "array")
|
|
545
|
-
|
|
545
|
+
message2 = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`;
|
|
546
546
|
else if (issue.type === "string")
|
|
547
|
-
|
|
547
|
+
message2 = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;
|
|
548
548
|
else if (issue.type === "number")
|
|
549
|
-
|
|
549
|
+
message2 = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
|
|
550
550
|
else if (issue.type === "bigint")
|
|
551
|
-
|
|
551
|
+
message2 = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
|
|
552
552
|
else if (issue.type === "date")
|
|
553
|
-
|
|
553
|
+
message2 = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`;
|
|
554
554
|
else
|
|
555
|
-
|
|
555
|
+
message2 = "Invalid input";
|
|
556
556
|
break;
|
|
557
557
|
case ZodIssueCode.too_big:
|
|
558
558
|
if (issue.type === "array")
|
|
559
|
-
|
|
559
|
+
message2 = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`;
|
|
560
560
|
else if (issue.type === "string")
|
|
561
|
-
|
|
561
|
+
message2 = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`;
|
|
562
562
|
else if (issue.type === "number")
|
|
563
|
-
|
|
563
|
+
message2 = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
|
|
564
564
|
else if (issue.type === "bigint")
|
|
565
|
-
|
|
565
|
+
message2 = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
|
|
566
566
|
else if (issue.type === "date")
|
|
567
|
-
|
|
567
|
+
message2 = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`;
|
|
568
568
|
else
|
|
569
|
-
|
|
569
|
+
message2 = "Invalid input";
|
|
570
570
|
break;
|
|
571
571
|
case ZodIssueCode.custom:
|
|
572
|
-
|
|
572
|
+
message2 = `Invalid input`;
|
|
573
573
|
break;
|
|
574
574
|
case ZodIssueCode.invalid_intersection_types:
|
|
575
|
-
|
|
575
|
+
message2 = `Intersection results could not be merged`;
|
|
576
576
|
break;
|
|
577
577
|
case ZodIssueCode.not_multiple_of:
|
|
578
|
-
|
|
578
|
+
message2 = `Number must be a multiple of ${issue.multipleOf}`;
|
|
579
579
|
break;
|
|
580
580
|
case ZodIssueCode.not_finite:
|
|
581
|
-
|
|
581
|
+
message2 = "Number must be finite";
|
|
582
582
|
break;
|
|
583
583
|
default:
|
|
584
|
-
|
|
584
|
+
message2 = _ctx.defaultError;
|
|
585
585
|
util.assertNever(issue);
|
|
586
586
|
}
|
|
587
|
-
return { message };
|
|
587
|
+
return { message: message2 };
|
|
588
588
|
};
|
|
589
589
|
var en_default = errorMap;
|
|
590
590
|
|
|
@@ -710,8 +710,8 @@ var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
|
710
710
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
711
711
|
var errorUtil;
|
|
712
712
|
(function(errorUtil2) {
|
|
713
|
-
errorUtil2.errToObj = (
|
|
714
|
-
errorUtil2.toString = (
|
|
713
|
+
errorUtil2.errToObj = (message2) => typeof message2 === "string" ? { message: message2 } : message2 || {};
|
|
714
|
+
errorUtil2.toString = (message2) => typeof message2 === "string" ? message2 : message2?.message;
|
|
715
715
|
})(errorUtil || (errorUtil = {}));
|
|
716
716
|
|
|
717
717
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
@@ -763,16 +763,16 @@ function processCreateParams(params) {
|
|
|
763
763
|
if (errorMap2)
|
|
764
764
|
return { errorMap: errorMap2, description };
|
|
765
765
|
const customMap = (iss, ctx) => {
|
|
766
|
-
const { message } = params;
|
|
766
|
+
const { message: message2 } = params;
|
|
767
767
|
if (iss.code === "invalid_enum_value") {
|
|
768
|
-
return { message:
|
|
768
|
+
return { message: message2 ?? ctx.defaultError };
|
|
769
769
|
}
|
|
770
770
|
if (typeof ctx.data === "undefined") {
|
|
771
|
-
return { message:
|
|
771
|
+
return { message: message2 ?? required_error ?? ctx.defaultError };
|
|
772
772
|
}
|
|
773
773
|
if (iss.code !== "invalid_type")
|
|
774
774
|
return { message: ctx.defaultError };
|
|
775
|
-
return { message:
|
|
775
|
+
return { message: message2 ?? invalid_type_error ?? ctx.defaultError };
|
|
776
776
|
};
|
|
777
777
|
return { errorMap: customMap, description };
|
|
778
778
|
}
|
|
@@ -898,14 +898,14 @@ var ZodType = class {
|
|
|
898
898
|
const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult));
|
|
899
899
|
return handleResult(ctx, result);
|
|
900
900
|
}
|
|
901
|
-
refine(check,
|
|
901
|
+
refine(check, message2) {
|
|
902
902
|
const getIssueProperties = (val) => {
|
|
903
|
-
if (typeof
|
|
904
|
-
return { message };
|
|
905
|
-
} else if (typeof
|
|
906
|
-
return
|
|
903
|
+
if (typeof message2 === "string" || typeof message2 === "undefined") {
|
|
904
|
+
return { message: message2 };
|
|
905
|
+
} else if (typeof message2 === "function") {
|
|
906
|
+
return message2(val);
|
|
907
907
|
} else {
|
|
908
|
-
return
|
|
908
|
+
return message2;
|
|
909
909
|
}
|
|
910
910
|
};
|
|
911
911
|
return this._refinement((val, ctx) => {
|
|
@@ -1441,11 +1441,11 @@ var ZodString = class _ZodString extends ZodType {
|
|
|
1441
1441
|
}
|
|
1442
1442
|
return { status: status.value, value: input.data };
|
|
1443
1443
|
}
|
|
1444
|
-
_regex(regex, validation,
|
|
1444
|
+
_regex(regex, validation, message2) {
|
|
1445
1445
|
return this.refinement((data) => regex.test(data), {
|
|
1446
1446
|
validation,
|
|
1447
1447
|
code: ZodIssueCode.invalid_string,
|
|
1448
|
-
...errorUtil.errToObj(
|
|
1448
|
+
...errorUtil.errToObj(message2)
|
|
1449
1449
|
});
|
|
1450
1450
|
}
|
|
1451
1451
|
_addCheck(check) {
|
|
@@ -1454,37 +1454,37 @@ var ZodString = class _ZodString extends ZodType {
|
|
|
1454
1454
|
checks: [...this._def.checks, check]
|
|
1455
1455
|
});
|
|
1456
1456
|
}
|
|
1457
|
-
email(
|
|
1458
|
-
return this._addCheck({ kind: "email", ...errorUtil.errToObj(
|
|
1457
|
+
email(message2) {
|
|
1458
|
+
return this._addCheck({ kind: "email", ...errorUtil.errToObj(message2) });
|
|
1459
1459
|
}
|
|
1460
|
-
url(
|
|
1461
|
-
return this._addCheck({ kind: "url", ...errorUtil.errToObj(
|
|
1460
|
+
url(message2) {
|
|
1461
|
+
return this._addCheck({ kind: "url", ...errorUtil.errToObj(message2) });
|
|
1462
1462
|
}
|
|
1463
|
-
emoji(
|
|
1464
|
-
return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(
|
|
1463
|
+
emoji(message2) {
|
|
1464
|
+
return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message2) });
|
|
1465
1465
|
}
|
|
1466
|
-
uuid(
|
|
1467
|
-
return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(
|
|
1466
|
+
uuid(message2) {
|
|
1467
|
+
return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message2) });
|
|
1468
1468
|
}
|
|
1469
|
-
nanoid(
|
|
1470
|
-
return this._addCheck({ kind: "nanoid", ...errorUtil.errToObj(
|
|
1469
|
+
nanoid(message2) {
|
|
1470
|
+
return this._addCheck({ kind: "nanoid", ...errorUtil.errToObj(message2) });
|
|
1471
1471
|
}
|
|
1472
|
-
cuid(
|
|
1473
|
-
return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(
|
|
1472
|
+
cuid(message2) {
|
|
1473
|
+
return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message2) });
|
|
1474
1474
|
}
|
|
1475
|
-
cuid2(
|
|
1476
|
-
return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(
|
|
1475
|
+
cuid2(message2) {
|
|
1476
|
+
return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message2) });
|
|
1477
1477
|
}
|
|
1478
|
-
ulid(
|
|
1479
|
-
return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(
|
|
1478
|
+
ulid(message2) {
|
|
1479
|
+
return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message2) });
|
|
1480
1480
|
}
|
|
1481
|
-
base64(
|
|
1482
|
-
return this._addCheck({ kind: "base64", ...errorUtil.errToObj(
|
|
1481
|
+
base64(message2) {
|
|
1482
|
+
return this._addCheck({ kind: "base64", ...errorUtil.errToObj(message2) });
|
|
1483
1483
|
}
|
|
1484
|
-
base64url(
|
|
1484
|
+
base64url(message2) {
|
|
1485
1485
|
return this._addCheck({
|
|
1486
1486
|
kind: "base64url",
|
|
1487
|
-
...errorUtil.errToObj(
|
|
1487
|
+
...errorUtil.errToObj(message2)
|
|
1488
1488
|
});
|
|
1489
1489
|
}
|
|
1490
1490
|
jwt(options) {
|
|
@@ -1514,8 +1514,8 @@ var ZodString = class _ZodString extends ZodType {
|
|
|
1514
1514
|
...errorUtil.errToObj(options?.message)
|
|
1515
1515
|
});
|
|
1516
1516
|
}
|
|
1517
|
-
date(
|
|
1518
|
-
return this._addCheck({ kind: "date", message });
|
|
1517
|
+
date(message2) {
|
|
1518
|
+
return this._addCheck({ kind: "date", message: message2 });
|
|
1519
1519
|
}
|
|
1520
1520
|
time(options) {
|
|
1521
1521
|
if (typeof options === "string") {
|
|
@@ -1531,14 +1531,14 @@ var ZodString = class _ZodString extends ZodType {
|
|
|
1531
1531
|
...errorUtil.errToObj(options?.message)
|
|
1532
1532
|
});
|
|
1533
1533
|
}
|
|
1534
|
-
duration(
|
|
1535
|
-
return this._addCheck({ kind: "duration", ...errorUtil.errToObj(
|
|
1534
|
+
duration(message2) {
|
|
1535
|
+
return this._addCheck({ kind: "duration", ...errorUtil.errToObj(message2) });
|
|
1536
1536
|
}
|
|
1537
|
-
regex(regex,
|
|
1537
|
+
regex(regex, message2) {
|
|
1538
1538
|
return this._addCheck({
|
|
1539
1539
|
kind: "regex",
|
|
1540
1540
|
regex,
|
|
1541
|
-
...errorUtil.errToObj(
|
|
1541
|
+
...errorUtil.errToObj(message2)
|
|
1542
1542
|
});
|
|
1543
1543
|
}
|
|
1544
1544
|
includes(value, options) {
|
|
@@ -1549,46 +1549,46 @@ var ZodString = class _ZodString extends ZodType {
|
|
|
1549
1549
|
...errorUtil.errToObj(options?.message)
|
|
1550
1550
|
});
|
|
1551
1551
|
}
|
|
1552
|
-
startsWith(value,
|
|
1552
|
+
startsWith(value, message2) {
|
|
1553
1553
|
return this._addCheck({
|
|
1554
1554
|
kind: "startsWith",
|
|
1555
1555
|
value,
|
|
1556
|
-
...errorUtil.errToObj(
|
|
1556
|
+
...errorUtil.errToObj(message2)
|
|
1557
1557
|
});
|
|
1558
1558
|
}
|
|
1559
|
-
endsWith(value,
|
|
1559
|
+
endsWith(value, message2) {
|
|
1560
1560
|
return this._addCheck({
|
|
1561
1561
|
kind: "endsWith",
|
|
1562
1562
|
value,
|
|
1563
|
-
...errorUtil.errToObj(
|
|
1563
|
+
...errorUtil.errToObj(message2)
|
|
1564
1564
|
});
|
|
1565
1565
|
}
|
|
1566
|
-
min(minLength,
|
|
1566
|
+
min(minLength, message2) {
|
|
1567
1567
|
return this._addCheck({
|
|
1568
1568
|
kind: "min",
|
|
1569
1569
|
value: minLength,
|
|
1570
|
-
...errorUtil.errToObj(
|
|
1570
|
+
...errorUtil.errToObj(message2)
|
|
1571
1571
|
});
|
|
1572
1572
|
}
|
|
1573
|
-
max(maxLength,
|
|
1573
|
+
max(maxLength, message2) {
|
|
1574
1574
|
return this._addCheck({
|
|
1575
1575
|
kind: "max",
|
|
1576
1576
|
value: maxLength,
|
|
1577
|
-
...errorUtil.errToObj(
|
|
1577
|
+
...errorUtil.errToObj(message2)
|
|
1578
1578
|
});
|
|
1579
1579
|
}
|
|
1580
|
-
length(len,
|
|
1580
|
+
length(len, message2) {
|
|
1581
1581
|
return this._addCheck({
|
|
1582
1582
|
kind: "length",
|
|
1583
1583
|
value: len,
|
|
1584
|
-
...errorUtil.errToObj(
|
|
1584
|
+
...errorUtil.errToObj(message2)
|
|
1585
1585
|
});
|
|
1586
1586
|
}
|
|
1587
1587
|
/**
|
|
1588
1588
|
* Equivalent to `.min(1)`
|
|
1589
1589
|
*/
|
|
1590
|
-
nonempty(
|
|
1591
|
-
return this.min(1, errorUtil.errToObj(
|
|
1590
|
+
nonempty(message2) {
|
|
1591
|
+
return this.min(1, errorUtil.errToObj(message2));
|
|
1592
1592
|
}
|
|
1593
1593
|
trim() {
|
|
1594
1594
|
return new _ZodString({
|
|
@@ -1781,19 +1781,19 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
1781
1781
|
}
|
|
1782
1782
|
return { status: status.value, value: input.data };
|
|
1783
1783
|
}
|
|
1784
|
-
gte(value,
|
|
1785
|
-
return this.setLimit("min", value, true, errorUtil.toString(
|
|
1784
|
+
gte(value, message2) {
|
|
1785
|
+
return this.setLimit("min", value, true, errorUtil.toString(message2));
|
|
1786
1786
|
}
|
|
1787
|
-
gt(value,
|
|
1788
|
-
return this.setLimit("min", value, false, errorUtil.toString(
|
|
1787
|
+
gt(value, message2) {
|
|
1788
|
+
return this.setLimit("min", value, false, errorUtil.toString(message2));
|
|
1789
1789
|
}
|
|
1790
|
-
lte(value,
|
|
1791
|
-
return this.setLimit("max", value, true, errorUtil.toString(
|
|
1790
|
+
lte(value, message2) {
|
|
1791
|
+
return this.setLimit("max", value, true, errorUtil.toString(message2));
|
|
1792
1792
|
}
|
|
1793
|
-
lt(value,
|
|
1794
|
-
return this.setLimit("max", value, false, errorUtil.toString(
|
|
1793
|
+
lt(value, message2) {
|
|
1794
|
+
return this.setLimit("max", value, false, errorUtil.toString(message2));
|
|
1795
1795
|
}
|
|
1796
|
-
setLimit(kind, value, inclusive,
|
|
1796
|
+
setLimit(kind, value, inclusive, message2) {
|
|
1797
1797
|
return new _ZodNumber({
|
|
1798
1798
|
...this._def,
|
|
1799
1799
|
checks: [
|
|
@@ -1802,7 +1802,7 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
1802
1802
|
kind,
|
|
1803
1803
|
value,
|
|
1804
1804
|
inclusive,
|
|
1805
|
-
message: errorUtil.toString(
|
|
1805
|
+
message: errorUtil.toString(message2)
|
|
1806
1806
|
}
|
|
1807
1807
|
]
|
|
1808
1808
|
});
|
|
@@ -1813,68 +1813,68 @@ var ZodNumber = class _ZodNumber extends ZodType {
|
|
|
1813
1813
|
checks: [...this._def.checks, check]
|
|
1814
1814
|
});
|
|
1815
1815
|
}
|
|
1816
|
-
int(
|
|
1816
|
+
int(message2) {
|
|
1817
1817
|
return this._addCheck({
|
|
1818
1818
|
kind: "int",
|
|
1819
|
-
message: errorUtil.toString(
|
|
1819
|
+
message: errorUtil.toString(message2)
|
|
1820
1820
|
});
|
|
1821
1821
|
}
|
|
1822
|
-
positive(
|
|
1822
|
+
positive(message2) {
|
|
1823
1823
|
return this._addCheck({
|
|
1824
1824
|
kind: "min",
|
|
1825
1825
|
value: 0,
|
|
1826
1826
|
inclusive: false,
|
|
1827
|
-
message: errorUtil.toString(
|
|
1827
|
+
message: errorUtil.toString(message2)
|
|
1828
1828
|
});
|
|
1829
1829
|
}
|
|
1830
|
-
negative(
|
|
1830
|
+
negative(message2) {
|
|
1831
1831
|
return this._addCheck({
|
|
1832
1832
|
kind: "max",
|
|
1833
1833
|
value: 0,
|
|
1834
1834
|
inclusive: false,
|
|
1835
|
-
message: errorUtil.toString(
|
|
1835
|
+
message: errorUtil.toString(message2)
|
|
1836
1836
|
});
|
|
1837
1837
|
}
|
|
1838
|
-
nonpositive(
|
|
1838
|
+
nonpositive(message2) {
|
|
1839
1839
|
return this._addCheck({
|
|
1840
1840
|
kind: "max",
|
|
1841
1841
|
value: 0,
|
|
1842
1842
|
inclusive: true,
|
|
1843
|
-
message: errorUtil.toString(
|
|
1843
|
+
message: errorUtil.toString(message2)
|
|
1844
1844
|
});
|
|
1845
1845
|
}
|
|
1846
|
-
nonnegative(
|
|
1846
|
+
nonnegative(message2) {
|
|
1847
1847
|
return this._addCheck({
|
|
1848
1848
|
kind: "min",
|
|
1849
1849
|
value: 0,
|
|
1850
1850
|
inclusive: true,
|
|
1851
|
-
message: errorUtil.toString(
|
|
1851
|
+
message: errorUtil.toString(message2)
|
|
1852
1852
|
});
|
|
1853
1853
|
}
|
|
1854
|
-
multipleOf(value,
|
|
1854
|
+
multipleOf(value, message2) {
|
|
1855
1855
|
return this._addCheck({
|
|
1856
1856
|
kind: "multipleOf",
|
|
1857
1857
|
value,
|
|
1858
|
-
message: errorUtil.toString(
|
|
1858
|
+
message: errorUtil.toString(message2)
|
|
1859
1859
|
});
|
|
1860
1860
|
}
|
|
1861
|
-
finite(
|
|
1861
|
+
finite(message2) {
|
|
1862
1862
|
return this._addCheck({
|
|
1863
1863
|
kind: "finite",
|
|
1864
|
-
message: errorUtil.toString(
|
|
1864
|
+
message: errorUtil.toString(message2)
|
|
1865
1865
|
});
|
|
1866
1866
|
}
|
|
1867
|
-
safe(
|
|
1867
|
+
safe(message2) {
|
|
1868
1868
|
return this._addCheck({
|
|
1869
1869
|
kind: "min",
|
|
1870
1870
|
inclusive: true,
|
|
1871
1871
|
value: Number.MIN_SAFE_INTEGER,
|
|
1872
|
-
message: errorUtil.toString(
|
|
1872
|
+
message: errorUtil.toString(message2)
|
|
1873
1873
|
})._addCheck({
|
|
1874
1874
|
kind: "max",
|
|
1875
1875
|
inclusive: true,
|
|
1876
1876
|
value: Number.MAX_SAFE_INTEGER,
|
|
1877
|
-
message: errorUtil.toString(
|
|
1877
|
+
message: errorUtil.toString(message2)
|
|
1878
1878
|
});
|
|
1879
1879
|
}
|
|
1880
1880
|
get minValue() {
|
|
@@ -1997,19 +1997,19 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
|
|
|
1997
1997
|
});
|
|
1998
1998
|
return INVALID;
|
|
1999
1999
|
}
|
|
2000
|
-
gte(value,
|
|
2001
|
-
return this.setLimit("min", value, true, errorUtil.toString(
|
|
2000
|
+
gte(value, message2) {
|
|
2001
|
+
return this.setLimit("min", value, true, errorUtil.toString(message2));
|
|
2002
2002
|
}
|
|
2003
|
-
gt(value,
|
|
2004
|
-
return this.setLimit("min", value, false, errorUtil.toString(
|
|
2003
|
+
gt(value, message2) {
|
|
2004
|
+
return this.setLimit("min", value, false, errorUtil.toString(message2));
|
|
2005
2005
|
}
|
|
2006
|
-
lte(value,
|
|
2007
|
-
return this.setLimit("max", value, true, errorUtil.toString(
|
|
2006
|
+
lte(value, message2) {
|
|
2007
|
+
return this.setLimit("max", value, true, errorUtil.toString(message2));
|
|
2008
2008
|
}
|
|
2009
|
-
lt(value,
|
|
2010
|
-
return this.setLimit("max", value, false, errorUtil.toString(
|
|
2009
|
+
lt(value, message2) {
|
|
2010
|
+
return this.setLimit("max", value, false, errorUtil.toString(message2));
|
|
2011
2011
|
}
|
|
2012
|
-
setLimit(kind, value, inclusive,
|
|
2012
|
+
setLimit(kind, value, inclusive, message2) {
|
|
2013
2013
|
return new _ZodBigInt({
|
|
2014
2014
|
...this._def,
|
|
2015
2015
|
checks: [
|
|
@@ -2018,7 +2018,7 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
|
|
|
2018
2018
|
kind,
|
|
2019
2019
|
value,
|
|
2020
2020
|
inclusive,
|
|
2021
|
-
message: errorUtil.toString(
|
|
2021
|
+
message: errorUtil.toString(message2)
|
|
2022
2022
|
}
|
|
2023
2023
|
]
|
|
2024
2024
|
});
|
|
@@ -2029,43 +2029,43 @@ var ZodBigInt = class _ZodBigInt extends ZodType {
|
|
|
2029
2029
|
checks: [...this._def.checks, check]
|
|
2030
2030
|
});
|
|
2031
2031
|
}
|
|
2032
|
-
positive(
|
|
2032
|
+
positive(message2) {
|
|
2033
2033
|
return this._addCheck({
|
|
2034
2034
|
kind: "min",
|
|
2035
2035
|
value: BigInt(0),
|
|
2036
2036
|
inclusive: false,
|
|
2037
|
-
message: errorUtil.toString(
|
|
2037
|
+
message: errorUtil.toString(message2)
|
|
2038
2038
|
});
|
|
2039
2039
|
}
|
|
2040
|
-
negative(
|
|
2040
|
+
negative(message2) {
|
|
2041
2041
|
return this._addCheck({
|
|
2042
2042
|
kind: "max",
|
|
2043
2043
|
value: BigInt(0),
|
|
2044
2044
|
inclusive: false,
|
|
2045
|
-
message: errorUtil.toString(
|
|
2045
|
+
message: errorUtil.toString(message2)
|
|
2046
2046
|
});
|
|
2047
2047
|
}
|
|
2048
|
-
nonpositive(
|
|
2048
|
+
nonpositive(message2) {
|
|
2049
2049
|
return this._addCheck({
|
|
2050
2050
|
kind: "max",
|
|
2051
2051
|
value: BigInt(0),
|
|
2052
2052
|
inclusive: true,
|
|
2053
|
-
message: errorUtil.toString(
|
|
2053
|
+
message: errorUtil.toString(message2)
|
|
2054
2054
|
});
|
|
2055
2055
|
}
|
|
2056
|
-
nonnegative(
|
|
2056
|
+
nonnegative(message2) {
|
|
2057
2057
|
return this._addCheck({
|
|
2058
2058
|
kind: "min",
|
|
2059
2059
|
value: BigInt(0),
|
|
2060
2060
|
inclusive: true,
|
|
2061
|
-
message: errorUtil.toString(
|
|
2061
|
+
message: errorUtil.toString(message2)
|
|
2062
2062
|
});
|
|
2063
2063
|
}
|
|
2064
|
-
multipleOf(value,
|
|
2064
|
+
multipleOf(value, message2) {
|
|
2065
2065
|
return this._addCheck({
|
|
2066
2066
|
kind: "multipleOf",
|
|
2067
2067
|
value,
|
|
2068
|
-
message: errorUtil.toString(
|
|
2068
|
+
message: errorUtil.toString(message2)
|
|
2069
2069
|
});
|
|
2070
2070
|
}
|
|
2071
2071
|
get minValue() {
|
|
@@ -2188,18 +2188,18 @@ var ZodDate = class _ZodDate extends ZodType {
|
|
|
2188
2188
|
checks: [...this._def.checks, check]
|
|
2189
2189
|
});
|
|
2190
2190
|
}
|
|
2191
|
-
min(minDate,
|
|
2191
|
+
min(minDate, message2) {
|
|
2192
2192
|
return this._addCheck({
|
|
2193
2193
|
kind: "min",
|
|
2194
2194
|
value: minDate.getTime(),
|
|
2195
|
-
message: errorUtil.toString(
|
|
2195
|
+
message: errorUtil.toString(message2)
|
|
2196
2196
|
});
|
|
2197
2197
|
}
|
|
2198
|
-
max(maxDate,
|
|
2198
|
+
max(maxDate, message2) {
|
|
2199
2199
|
return this._addCheck({
|
|
2200
2200
|
kind: "max",
|
|
2201
2201
|
value: maxDate.getTime(),
|
|
2202
|
-
message: errorUtil.toString(
|
|
2202
|
+
message: errorUtil.toString(message2)
|
|
2203
2203
|
});
|
|
2204
2204
|
}
|
|
2205
2205
|
get minDate() {
|
|
@@ -2431,26 +2431,26 @@ var ZodArray = class _ZodArray extends ZodType {
|
|
|
2431
2431
|
get element() {
|
|
2432
2432
|
return this._def.type;
|
|
2433
2433
|
}
|
|
2434
|
-
min(minLength,
|
|
2434
|
+
min(minLength, message2) {
|
|
2435
2435
|
return new _ZodArray({
|
|
2436
2436
|
...this._def,
|
|
2437
|
-
minLength: { value: minLength, message: errorUtil.toString(
|
|
2437
|
+
minLength: { value: minLength, message: errorUtil.toString(message2) }
|
|
2438
2438
|
});
|
|
2439
2439
|
}
|
|
2440
|
-
max(maxLength,
|
|
2440
|
+
max(maxLength, message2) {
|
|
2441
2441
|
return new _ZodArray({
|
|
2442
2442
|
...this._def,
|
|
2443
|
-
maxLength: { value: maxLength, message: errorUtil.toString(
|
|
2443
|
+
maxLength: { value: maxLength, message: errorUtil.toString(message2) }
|
|
2444
2444
|
});
|
|
2445
2445
|
}
|
|
2446
|
-
length(len,
|
|
2446
|
+
length(len, message2) {
|
|
2447
2447
|
return new _ZodArray({
|
|
2448
2448
|
...this._def,
|
|
2449
|
-
exactLength: { value: len, message: errorUtil.toString(
|
|
2449
|
+
exactLength: { value: len, message: errorUtil.toString(message2) }
|
|
2450
2450
|
});
|
|
2451
2451
|
}
|
|
2452
|
-
nonempty(
|
|
2453
|
-
return this.min(1,
|
|
2452
|
+
nonempty(message2) {
|
|
2453
|
+
return this.min(1, message2);
|
|
2454
2454
|
}
|
|
2455
2455
|
};
|
|
2456
2456
|
ZodArray.create = (schema, params) => {
|
|
@@ -2593,17 +2593,17 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
2593
2593
|
get shape() {
|
|
2594
2594
|
return this._def.shape();
|
|
2595
2595
|
}
|
|
2596
|
-
strict(
|
|
2596
|
+
strict(message2) {
|
|
2597
2597
|
errorUtil.errToObj;
|
|
2598
2598
|
return new _ZodObject({
|
|
2599
2599
|
...this._def,
|
|
2600
2600
|
unknownKeys: "strict",
|
|
2601
|
-
...
|
|
2601
|
+
...message2 !== void 0 ? {
|
|
2602
2602
|
errorMap: (issue, ctx) => {
|
|
2603
2603
|
const defaultError = this._def.errorMap?.(issue, ctx).message ?? ctx.defaultError;
|
|
2604
2604
|
if (issue.code === "unrecognized_keys")
|
|
2605
2605
|
return {
|
|
2606
|
-
message: errorUtil.errToObj(
|
|
2606
|
+
message: errorUtil.errToObj(message2).message ?? defaultError
|
|
2607
2607
|
};
|
|
2608
2608
|
return {
|
|
2609
2609
|
message: defaultError
|
|
@@ -3359,23 +3359,23 @@ var ZodSet = class _ZodSet extends ZodType {
|
|
|
3359
3359
|
return finalizeSet(elements);
|
|
3360
3360
|
}
|
|
3361
3361
|
}
|
|
3362
|
-
min(minSize,
|
|
3362
|
+
min(minSize, message2) {
|
|
3363
3363
|
return new _ZodSet({
|
|
3364
3364
|
...this._def,
|
|
3365
|
-
minSize: { value: minSize, message: errorUtil.toString(
|
|
3365
|
+
minSize: { value: minSize, message: errorUtil.toString(message2) }
|
|
3366
3366
|
});
|
|
3367
3367
|
}
|
|
3368
|
-
max(maxSize,
|
|
3368
|
+
max(maxSize, message2) {
|
|
3369
3369
|
return new _ZodSet({
|
|
3370
3370
|
...this._def,
|
|
3371
|
-
maxSize: { value: maxSize, message: errorUtil.toString(
|
|
3371
|
+
maxSize: { value: maxSize, message: errorUtil.toString(message2) }
|
|
3372
3372
|
});
|
|
3373
3373
|
}
|
|
3374
|
-
size(size,
|
|
3375
|
-
return this.min(size,
|
|
3374
|
+
size(size, message2) {
|
|
3375
|
+
return this.min(size, message2).max(size, message2);
|
|
3376
3376
|
}
|
|
3377
|
-
nonempty(
|
|
3378
|
-
return this.min(1,
|
|
3377
|
+
nonempty(message2) {
|
|
3378
|
+
return this.min(1, message2);
|
|
3379
3379
|
}
|
|
3380
3380
|
};
|
|
3381
3381
|
ZodSet.create = (valueType, params) => {
|
|
@@ -4162,7 +4162,7 @@ var coerce = {
|
|
|
4162
4162
|
};
|
|
4163
4163
|
var NEVER = INVALID;
|
|
4164
4164
|
|
|
4165
|
-
// ../shared/dist/chunk-
|
|
4165
|
+
// ../shared/dist/chunk-VSUBG7GR.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(),
|
|
@@ -4780,7 +4789,8 @@ var DiscoveredPortSchema = external_exports.object({
|
|
|
4780
4789
|
port: external_exports.number().int().min(1).max(65535),
|
|
4781
4790
|
label: external_exports.string().min(1).max(64).optional(),
|
|
4782
4791
|
protocol: external_exports.enum(["http", "tcp"]).optional(),
|
|
4783
|
-
detectedAt: external_exports.string()
|
|
4792
|
+
detectedAt: external_exports.string(),
|
|
4793
|
+
hidden: external_exports.boolean().optional()
|
|
4784
4794
|
});
|
|
4785
4795
|
var ReportDiscoveredPortsRequestSchema = external_exports.object({
|
|
4786
4796
|
sessionId: external_exports.string(),
|
|
@@ -4927,7 +4937,11 @@ var SubmitCodeReviewResultRequestSchema = external_exports.object({
|
|
|
4927
4937
|
var CycleCodingAgentKeyRequestSchema = external_exports.object({
|
|
4928
4938
|
sessionId: external_exports.string(),
|
|
4929
4939
|
rateLimitType: external_exports.string(),
|
|
4930
|
-
resetsAt: external_exports.string().optional()
|
|
4940
|
+
resetsAt: external_exports.string().optional(),
|
|
4941
|
+
// The key the pod is actually running (CONVEYOR_CODING_AGENT_KEY_ID). Wins
|
|
4942
|
+
// over the session's attribution stamp after owner validation; optional so
|
|
4943
|
+
// older agents keep the session fallback.
|
|
4944
|
+
codingAgentKeyId: external_exports.string().optional()
|
|
4931
4945
|
});
|
|
4932
4946
|
var PostChildChatMessageRequestSchema = external_exports.object({
|
|
4933
4947
|
sessionId: external_exports.string(),
|
|
@@ -5341,11 +5355,12 @@ var QueryProjectGcpLogsRequestSchema = external_exports.object({
|
|
|
5341
5355
|
sqlInstances: external_exports.array(external_exports.string().min(1).max(200)).max(25).optional(),
|
|
5342
5356
|
allServices: external_exports.boolean().optional(),
|
|
5343
5357
|
search: external_exports.string().max(256).optional(),
|
|
5344
|
-
filter: external_exports.string().max(
|
|
5358
|
+
filter: external_exports.string().max(2e3).optional(),
|
|
5345
5359
|
startTime: external_exports.string().optional(),
|
|
5346
5360
|
endTime: external_exports.string().optional(),
|
|
5347
5361
|
limit: external_exports.number().int().min(1).max(200).optional().default(50),
|
|
5348
|
-
pageToken: external_exports.string().max(4096).optional()
|
|
5362
|
+
pageToken: external_exports.string().max(4096).optional(),
|
|
5363
|
+
full: external_exports.boolean().optional()
|
|
5349
5364
|
});
|
|
5350
5365
|
var QueryProjectGrafanaLogsRequestSchema = external_exports.object({
|
|
5351
5366
|
projectId: external_exports.string(),
|
|
@@ -6260,6 +6275,8 @@ var ANTHROPIC_CATALOG = [
|
|
|
6260
6275
|
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
6261
6276
|
anthropicEntry(FABLE_MODEL, "Fable 5.1", 10, 50)
|
|
6262
6277
|
];
|
|
6278
|
+
var PROJECT_ARCHIVE_RETENTION_DAYS = 30;
|
|
6279
|
+
var RETENTION_MS = PROJECT_ARCHIVE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
6263
6280
|
var CLAUDESPACE_WORKLOAD_LABEL = "rc-workload";
|
|
6264
6281
|
var CLAUDESPACE_WORKLOAD_VALUE = "claudespace";
|
|
6265
6282
|
var CONVEYOR_POD_SELECTOR = `${CLAUDESPACE_WORKLOAD_LABEL}=${CLAUDESPACE_WORKLOAD_VALUE}`;
|
|
@@ -6286,10 +6303,13 @@ var PERSONAL_LOCAL_REGISTRY_NAME = "conveyor-registry";
|
|
|
6286
6303
|
var PERSONAL_LOCAL_REGISTRY_PORT = 5e3;
|
|
6287
6304
|
var PERSONAL_LOCAL_REGISTRY_HOST = `${PERSONAL_LOCAL_REGISTRY_NAME}.${PERSONAL_LOCAL_REGISTRY_NAMESPACE}.svc.cluster.local:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6288
6305
|
var PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT = `http://127.0.0.1:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6306
|
+
var PERSONAL_LOCAL_REGISTRY_PUSH_SECRET = "conveyor-registry-push";
|
|
6307
|
+
var PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY = "config.json";
|
|
6289
6308
|
var BAKE_COMPONENT_LABEL = "claudespace.rallycry.com/component";
|
|
6290
6309
|
var BAKE_COMPONENT_PREBAKE = "prebake";
|
|
6291
6310
|
var BAKE_COMPONENT_STATE_BAKE = "state-bake";
|
|
6292
6311
|
var BAKE_COMPONENT_VALUES = [BAKE_COMPONENT_PREBAKE, BAKE_COMPONENT_STATE_BAKE];
|
|
6312
|
+
var PERSONAL_BAKE_NAMESPACE = "conveyor-bakes";
|
|
6293
6313
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
6294
6314
|
var POSTGRES_ENV = {
|
|
6295
6315
|
POSTGRES_HOST_AUTH_METHOD: "trust",
|
|
@@ -6608,6 +6628,8 @@ var REPORT_CARD_TYPES = surfaceTypes("report");
|
|
|
6608
6628
|
var DECISION_CARD_TYPES = surfaceTypes("decision");
|
|
6609
6629
|
|
|
6610
6630
|
// src/daemon/connection.ts
|
|
6631
|
+
var REDIAL_FIRST_MS = 5e3;
|
|
6632
|
+
var REDIAL_MAX_MS = 5 * 6e4;
|
|
6611
6633
|
function unwrap(ack, what) {
|
|
6612
6634
|
if (!ack?.ok || ack.data === void 0) {
|
|
6613
6635
|
throw new Error(`${what} failed: ${ack?.error ?? "no response"}`);
|
|
@@ -6617,6 +6639,8 @@ function unwrap(ack, what) {
|
|
|
6617
6639
|
var ClusterConnection = class {
|
|
6618
6640
|
socket = null;
|
|
6619
6641
|
config;
|
|
6642
|
+
redialDelayMs = REDIAL_FIRST_MS;
|
|
6643
|
+
redialTimer = null;
|
|
6620
6644
|
/** Called when Conveyor says the desired state changed. */
|
|
6621
6645
|
onSyncRequested = null;
|
|
6622
6646
|
/** Called when Conveyor asks the machine to remove itself. */
|
|
@@ -6652,6 +6676,7 @@ var ClusterConnection = class {
|
|
|
6652
6676
|
});
|
|
6653
6677
|
this.socket = socket;
|
|
6654
6678
|
socket.on("connect", () => {
|
|
6679
|
+
this.redialDelayMs = REDIAL_FIRST_MS;
|
|
6655
6680
|
if (!settled) {
|
|
6656
6681
|
settled = true;
|
|
6657
6682
|
resolve();
|
|
@@ -6670,6 +6695,9 @@ var ClusterConnection = class {
|
|
|
6670
6695
|
socket.on("disconnect", (reason) => {
|
|
6671
6696
|
process.stderr.write(`[conveyor-k3] disconnected: ${reason}
|
|
6672
6697
|
`);
|
|
6698
|
+
if (reason === "io server disconnect" && this.config.reconnectWhenReplaced) {
|
|
6699
|
+
this.redial(socket);
|
|
6700
|
+
}
|
|
6673
6701
|
});
|
|
6674
6702
|
socket.on(PERSONAL_CLUSTER_EVENTS.syncRequested, () => {
|
|
6675
6703
|
this.onSyncRequested?.();
|
|
@@ -6731,7 +6759,20 @@ var ClusterConnection = class {
|
|
|
6731
6759
|
} catch {
|
|
6732
6760
|
}
|
|
6733
6761
|
}
|
|
6762
|
+
/** Dial again after the server closed the socket, waiting longer each time. */
|
|
6763
|
+
redial(socket) {
|
|
6764
|
+
if (this.redialTimer) return;
|
|
6765
|
+
const delay = this.redialDelayMs;
|
|
6766
|
+
this.redialDelayMs = Math.min(delay * 2, REDIAL_MAX_MS);
|
|
6767
|
+
this.redialTimer = setTimeout(() => {
|
|
6768
|
+
this.redialTimer = null;
|
|
6769
|
+
if (this.socket === socket && !socket.connected) socket.connect();
|
|
6770
|
+
}, delay);
|
|
6771
|
+
this.redialTimer.unref?.();
|
|
6772
|
+
}
|
|
6734
6773
|
disconnect() {
|
|
6774
|
+
if (this.redialTimer) clearTimeout(this.redialTimer);
|
|
6775
|
+
this.redialTimer = null;
|
|
6735
6776
|
this.socket?.disconnect();
|
|
6736
6777
|
this.socket = null;
|
|
6737
6778
|
}
|
|
@@ -6929,14 +6970,22 @@ async function readImageInventory(exec) {
|
|
|
6929
6970
|
}
|
|
6930
6971
|
return { images, referencedIds };
|
|
6931
6972
|
}
|
|
6973
|
+
var CRICTL_RMI_TIMEOUT = "120s";
|
|
6974
|
+
var RMI_EXEC_TIMEOUT_MS = 15e4;
|
|
6975
|
+
var DEADLINE_EXCEEDED = "context deadline exceeded";
|
|
6932
6976
|
async function pruneStrandedImages(exec, inventory, wantedImages) {
|
|
6933
6977
|
const candidates = selectStrandedImages(inventory, wantedImages);
|
|
6934
|
-
const result = { removed: 0, bytesReclaimed: 0, failures: [] };
|
|
6978
|
+
const result = { removed: 0, bytesReclaimed: 0, failures: [], pending: [] };
|
|
6935
6979
|
for (const candidate of candidates) {
|
|
6936
|
-
const removal = await exec("crictl", ["rmi", candidate.id]
|
|
6980
|
+
const removal = await exec("crictl", ["--timeout", CRICTL_RMI_TIMEOUT, "rmi", candidate.id], {
|
|
6981
|
+
timeoutMs: RMI_EXEC_TIMEOUT_MS
|
|
6982
|
+
});
|
|
6937
6983
|
if (removal.exitCode === 0) {
|
|
6938
6984
|
result.removed += 1;
|
|
6939
6985
|
result.bytesReclaimed += candidate.sizeBytes;
|
|
6986
|
+
} else if (`${removal.stderr}
|
|
6987
|
+
${removal.stdout}`.includes(DEADLINE_EXCEEDED)) {
|
|
6988
|
+
result.pending.push(candidate.id);
|
|
6940
6989
|
} else {
|
|
6941
6990
|
result.failures.push(`${candidate.id}: ${removal.stderr.trim().slice(0, 120)}`);
|
|
6942
6991
|
}
|
|
@@ -7046,6 +7095,8 @@ var STATE_DIR = "/var/lib/conveyor-k3";
|
|
|
7046
7095
|
var STATE_FILE = `${STATE_DIR}/state.json`;
|
|
7047
7096
|
var LOCAL_REGISTRY_DATA_DIR = `${STATE_DIR}/registry`;
|
|
7048
7097
|
var LOCAL_REGISTRY_MIRROR_RESTART_FILE = `${STATE_DIR}/registry-mirror-restart`;
|
|
7098
|
+
var LOCAL_REGISTRY_CREDENTIALS_FILE = `${STATE_DIR}/registry-credentials.json`;
|
|
7099
|
+
var LOCAL_REGISTRY_AUTH_STATE_FILE = `${STATE_DIR}/registry-auth.json`;
|
|
7049
7100
|
var RANCHER_DIR = "/etc/rancher/k3s";
|
|
7050
7101
|
var K3S_OWNER_CONFIG = `${RANCHER_DIR}/config.yaml`;
|
|
7051
7102
|
var K3S_CONFIG_DROPIN_DIR = `${RANCHER_DIR}/config.yaml.d`;
|
|
@@ -7095,6 +7146,16 @@ function upsertRegistryAuth(file, host, auth) {
|
|
|
7095
7146
|
next.configs = configs;
|
|
7096
7147
|
return next;
|
|
7097
7148
|
}
|
|
7149
|
+
function removeRegistryAuth(file, host) {
|
|
7150
|
+
const existing = file.configs?.[host];
|
|
7151
|
+
if (!existing?.auth) return file;
|
|
7152
|
+
const { auth: _auth, ...rest } = existing;
|
|
7153
|
+
const { [host]: _removed, ...others } = file.configs ?? {};
|
|
7154
|
+
const configs = Object.keys(rest).length > 0 ? { ...others, [host]: rest } : others;
|
|
7155
|
+
const next = { ...file, configs };
|
|
7156
|
+
if (Object.keys(configs).length === 0) delete next.configs;
|
|
7157
|
+
return next;
|
|
7158
|
+
}
|
|
7098
7159
|
function mirrorEndpoints(file, host) {
|
|
7099
7160
|
const entry = file.mirrors?.[host];
|
|
7100
7161
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
@@ -7147,6 +7208,88 @@ async function writeState(fs, state) {
|
|
|
7147
7208
|
await fs.writeFile(STATE_FILE, `${JSON.stringify(state, null, 2)}
|
|
7148
7209
|
`, 384);
|
|
7149
7210
|
}
|
|
7211
|
+
function managesK3s(desired, state) {
|
|
7212
|
+
return desired.isolation === void 0 || state.k3sInstalledByConveyor === true;
|
|
7213
|
+
}
|
|
7214
|
+
|
|
7215
|
+
// src/lib/manifest-guard.ts
|
|
7216
|
+
import { isAlias, isMap, isScalar, parseAllDocuments } from "yaml";
|
|
7217
|
+
var ALLOWED_MANIFEST_KINDS = [
|
|
7218
|
+
"Namespace",
|
|
7219
|
+
"Role",
|
|
7220
|
+
"RoleBinding",
|
|
7221
|
+
"ClusterRole",
|
|
7222
|
+
"ClusterRoleBinding",
|
|
7223
|
+
"PriorityClass"
|
|
7224
|
+
];
|
|
7225
|
+
var ALLOWED = new Set(ALLOWED_MANIFEST_KINDS);
|
|
7226
|
+
var MISSING_KIND = "(missing kind)";
|
|
7227
|
+
var UNPARSEABLE = "(unparseable)";
|
|
7228
|
+
var LIST_SHAPED = "(list-shaped document)";
|
|
7229
|
+
function isListShaped(map) {
|
|
7230
|
+
return map.items.some(({ key }) => {
|
|
7231
|
+
if (isAlias(key)) return true;
|
|
7232
|
+
const name = isScalar(key) ? key.value : key;
|
|
7233
|
+
return typeof name === "string" && (name === "<<" || name.toLowerCase() === "items");
|
|
7234
|
+
});
|
|
7235
|
+
}
|
|
7236
|
+
function findDisallowedKinds(manifest) {
|
|
7237
|
+
let documents;
|
|
7238
|
+
try {
|
|
7239
|
+
documents = parseAllDocuments(manifest);
|
|
7240
|
+
} catch {
|
|
7241
|
+
return [UNPARSEABLE];
|
|
7242
|
+
}
|
|
7243
|
+
const disallowed = [];
|
|
7244
|
+
const record = (kind) => {
|
|
7245
|
+
if (!disallowed.includes(kind)) disallowed.push(kind);
|
|
7246
|
+
};
|
|
7247
|
+
for (const doc of documents) {
|
|
7248
|
+
if (doc.errors.length > 0) {
|
|
7249
|
+
record(UNPARSEABLE);
|
|
7250
|
+
continue;
|
|
7251
|
+
}
|
|
7252
|
+
if (doc.contents === null || doc.contents === void 0) continue;
|
|
7253
|
+
if (isScalar(doc.contents) && doc.contents.value === null) continue;
|
|
7254
|
+
if (!isMap(doc.contents)) {
|
|
7255
|
+
record(MISSING_KIND);
|
|
7256
|
+
continue;
|
|
7257
|
+
}
|
|
7258
|
+
if (isListShaped(doc.contents)) {
|
|
7259
|
+
record(LIST_SHAPED);
|
|
7260
|
+
continue;
|
|
7261
|
+
}
|
|
7262
|
+
const kind = doc.get("kind");
|
|
7263
|
+
if (typeof kind !== "string" || kind.length === 0) {
|
|
7264
|
+
record(MISSING_KIND);
|
|
7265
|
+
continue;
|
|
7266
|
+
}
|
|
7267
|
+
if (!ALLOWED.has(kind)) record(kind);
|
|
7268
|
+
}
|
|
7269
|
+
return disallowed;
|
|
7270
|
+
}
|
|
7271
|
+
function withoutKind(manifest, kind) {
|
|
7272
|
+
return parseAllDocuments(manifest).filter((doc) => !(isMap(doc.contents) && doc.get("kind") === kind)).map((doc) => doc.toString()).join("---\n");
|
|
7273
|
+
}
|
|
7274
|
+
function declaresPriorityClass(manifest, name, value) {
|
|
7275
|
+
return parseAllDocuments(manifest).some(
|
|
7276
|
+
(doc) => isMap(doc.contents) && doc.get("kind") === "PriorityClass" && doc.getIn(["metadata", "name"]) === name && doc.get("value") === value
|
|
7277
|
+
);
|
|
7278
|
+
}
|
|
7279
|
+
|
|
7280
|
+
// src/daemon/cluster-ops.ts
|
|
7281
|
+
async function kubectlApply(exec, manifest) {
|
|
7282
|
+
await runOrThrow(exec, "k3s", ["kubectl", "apply", "-f", "-"], {
|
|
7283
|
+
stdin: manifest,
|
|
7284
|
+
timeoutMs: 6e4
|
|
7285
|
+
});
|
|
7286
|
+
}
|
|
7287
|
+
function mayDisrupt(runningConveyorPods) {
|
|
7288
|
+
return runningConveyorPods === 0;
|
|
7289
|
+
}
|
|
7290
|
+
function busyReason(runningConveyorPods) {
|
|
7291
|
+
return runningConveyorPods === null ? "the number of running Conveyor pods could not be read" : `${runningConveyorPods} Conveyor pod(s) are running`;
|
|
7292
|
+
}
|
|
7150
7293
|
|
|
7151
7294
|
// src/lib/k3s-config.ts
|
|
7152
7295
|
var IMAGE_GC_HIGH = "image-gc-high-threshold";
|
|
@@ -7160,6 +7303,15 @@ var K3S_INSTALL_EXEC = [
|
|
|
7160
7303
|
`--${WRITE_KUBECONFIG_MODE}`,
|
|
7161
7304
|
KUBECONFIG_MODE
|
|
7162
7305
|
].join(" ");
|
|
7306
|
+
function bareToken(token) {
|
|
7307
|
+
let bare = token.trim();
|
|
7308
|
+
if (bare.endsWith("\\")) bare = bare.slice(0, -1).trim();
|
|
7309
|
+
const quote = bare[0];
|
|
7310
|
+
if (bare.length >= 2 && (quote === "'" || quote === '"') && bare.endsWith(quote)) {
|
|
7311
|
+
bare = bare.slice(1, -1);
|
|
7312
|
+
}
|
|
7313
|
+
return bare;
|
|
7314
|
+
}
|
|
7163
7315
|
function ownerConfiguredKubeletFlags(ownerConfig) {
|
|
7164
7316
|
const found = /* @__PURE__ */ new Set();
|
|
7165
7317
|
if (!ownerConfig) return found;
|
|
@@ -7185,7 +7337,7 @@ function ownerImageGcHighThreshold(ownerConfig) {
|
|
|
7185
7337
|
const value = Number.parseInt(match[2] ?? "", 10);
|
|
7186
7338
|
return Number.isInteger(value) && value >= 0 && value <= 100 ? value : null;
|
|
7187
7339
|
}
|
|
7188
|
-
function renderK3sDropIn(kubelet, ownerConfig) {
|
|
7340
|
+
function renderK3sDropIn(kubelet, ownerConfig, options = {}) {
|
|
7189
7341
|
const ownerFlags = ownerConfiguredKubeletFlags(ownerConfig);
|
|
7190
7342
|
const ownerKeys = ownerConfiguredKeys(ownerConfig);
|
|
7191
7343
|
const args = [];
|
|
@@ -7209,8 +7361,10 @@ function renderK3sDropIn(kubelet, ownerConfig) {
|
|
|
7209
7361
|
"# Your own /etc/rancher/k3s/config.yaml is never modified; k3s merges both.",
|
|
7210
7362
|
"# `kubelet-arg+` and `disable+` append rather than replacing a list you set."
|
|
7211
7363
|
];
|
|
7212
|
-
|
|
7213
|
-
|
|
7364
|
+
if (options.disableComponents !== false) {
|
|
7365
|
+
lines2.push("disable+:");
|
|
7366
|
+
for (const component of K3S_DISABLED_COMPONENTS) lines2.push(` - ${component}`);
|
|
7367
|
+
}
|
|
7214
7368
|
if (ownerKeys.has(WRITE_KUBECONFIG_MODE)) {
|
|
7215
7369
|
skipped.push(WRITE_KUBECONFIG_MODE);
|
|
7216
7370
|
} else {
|
|
@@ -7221,76 +7375,345 @@ function renderK3sDropIn(kubelet, ownerConfig) {
|
|
|
7221
7375
|
}
|
|
7222
7376
|
return { content: [...lines2, ""].join("\n"), skipped };
|
|
7223
7377
|
}
|
|
7224
|
-
var renderKubeletDropIn = renderK3sDropIn;
|
|
7225
7378
|
|
|
7226
|
-
// src/
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
"
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
"
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7379
|
+
// src/daemon/pod-cpu-quota.ts
|
|
7380
|
+
var POD_SLICE = "kubepods.slice";
|
|
7381
|
+
function cpuMillicores(quantity) {
|
|
7382
|
+
const checked = assertQuantity(quantity, "reserveCpu");
|
|
7383
|
+
const match = /^(\d+(?:\.\d+)?)(m?)$/.exec(checked);
|
|
7384
|
+
if (!match?.[1])
|
|
7385
|
+
throw new Error(`Invalid reserveCpu: ${JSON.stringify(checked)} is not a CPU amount`);
|
|
7386
|
+
const amount = Number.parseFloat(match[1]);
|
|
7387
|
+
return Math.round(match[2] === "m" ? amount : amount * 1e3);
|
|
7388
|
+
}
|
|
7389
|
+
function podCpuQuotaPercent(cores, reservedMillicores) {
|
|
7390
|
+
const left = cores * 1e3 - reservedMillicores;
|
|
7391
|
+
if (left < 10) return null;
|
|
7392
|
+
return Math.floor(left / 10);
|
|
7393
|
+
}
|
|
7394
|
+
function setQuota(exec, value) {
|
|
7395
|
+
return exec("systemctl", ["set-property", POD_SLICE, `CPUQuota=${value}`]);
|
|
7396
|
+
}
|
|
7397
|
+
async function liftPodCpuQuota(exec) {
|
|
7398
|
+
const shown = await exec("systemctl", ["show", POD_SLICE, "-p", "CPUQuotaPerSecUSec", "--value"]);
|
|
7399
|
+
if (shown.exitCode !== 0 || ["", "infinity"].includes(shown.stdout.trim())) return null;
|
|
7400
|
+
const cleared = await setQuota(exec, "");
|
|
7401
|
+
return cleared.exitCode === 0 ? null : cleared.stderr.trim().slice(0, 200);
|
|
7402
|
+
}
|
|
7403
|
+
async function reconcilePodCpuQuota(exec, desired, warnings) {
|
|
7404
|
+
const reserve = desired.kubelet.systemReserved?.cpu;
|
|
7405
|
+
if (!reserve) {
|
|
7406
|
+
const failure = await liftPodCpuQuota(exec);
|
|
7407
|
+
if (failure) warnings.push(`Could not lift the CPU cap on ${POD_SLICE}: ${failure}`);
|
|
7408
|
+
return void 0;
|
|
7245
7409
|
}
|
|
7246
|
-
const
|
|
7247
|
-
const
|
|
7248
|
-
|
|
7410
|
+
const reserved = cpuMillicores(reserve);
|
|
7411
|
+
const state = await exec("systemctl", ["show", POD_SLICE, "-p", "ActiveState", "--value"]);
|
|
7412
|
+
if (state.exitCode !== 0 || state.stdout.trim() !== "active") {
|
|
7413
|
+
warnings.push(
|
|
7414
|
+
`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.`
|
|
7415
|
+
);
|
|
7416
|
+
return false;
|
|
7417
|
+
}
|
|
7418
|
+
const nproc = await exec("nproc", []);
|
|
7419
|
+
const cores = Number.parseInt(nproc.stdout.trim(), 10);
|
|
7420
|
+
if (nproc.exitCode !== 0 || !Number.isInteger(cores) || cores <= 0) {
|
|
7421
|
+
warnings.push(
|
|
7422
|
+
"Could not count this machine's CPUs, so the CPU you keep is weighted, not capped."
|
|
7423
|
+
);
|
|
7424
|
+
return false;
|
|
7425
|
+
}
|
|
7426
|
+
const percent = podCpuQuotaPercent(cores, reserved);
|
|
7427
|
+
if (percent === null) {
|
|
7428
|
+
warnings.push(
|
|
7429
|
+
`The CPU you keep (${reserve}) leaves no CPU for builds on this ${cores}-CPU machine. Lower it in your Conveyor profile.`
|
|
7430
|
+
);
|
|
7431
|
+
return false;
|
|
7432
|
+
}
|
|
7433
|
+
const set = await setQuota(exec, `${percent}%`);
|
|
7434
|
+
if (set.exitCode !== 0) {
|
|
7435
|
+
warnings.push(
|
|
7436
|
+
`Could not cap pod CPU at ${percent / 100} CPUs: ${set.stderr.trim().slice(0, 200)}`
|
|
7437
|
+
);
|
|
7438
|
+
return false;
|
|
7439
|
+
}
|
|
7440
|
+
return true;
|
|
7441
|
+
}
|
|
7442
|
+
|
|
7443
|
+
// src/daemon/kubelet-upkeep.ts
|
|
7444
|
+
function ownerRestartWarning(change) {
|
|
7445
|
+
return `${change} Conveyor did not install this server's k3s, so it never restarts it: run \`sudo systemctl restart k3s\` when no build runs.`;
|
|
7446
|
+
}
|
|
7447
|
+
async function reconcileKubelet(ctx, desired, result, managed) {
|
|
7448
|
+
const ownerConfig = await ctx.fs.readFile(K3S_OWNER_CONFIG);
|
|
7449
|
+
const rendered = renderK3sDropIn(desired.kubelet, ownerConfig, { disableComponents: managed });
|
|
7450
|
+
result.skippedKubeletFlags = rendered.skipped;
|
|
7451
|
+
result.effectiveImageGcHighThreshold = rendered.skipped.includes("image-gc-high-threshold") ? ownerImageGcHighThreshold(ownerConfig) : desired.kubelet.imageGcHighThreshold;
|
|
7452
|
+
for (const flag of rendered.skipped) {
|
|
7453
|
+
result.warnings.push(
|
|
7454
|
+
`Left \`${flag}\` to your own /etc/rancher/k3s/config.yaml \u2014 Conveyor does not override a setting you chose.`
|
|
7455
|
+
);
|
|
7456
|
+
}
|
|
7457
|
+
const current = await ctx.fs.readFile(K3S_CONFIG_DROPIN);
|
|
7458
|
+
if (rendered.content === null) {
|
|
7459
|
+
if (current !== null) await ctx.fs.removeFile(K3S_CONFIG_DROPIN);
|
|
7460
|
+
return;
|
|
7461
|
+
}
|
|
7462
|
+
if (current === rendered.content) return;
|
|
7463
|
+
await ctx.fs.ensureDir(K3S_CONFIG_DROPIN.slice(0, K3S_CONFIG_DROPIN.lastIndexOf("/")));
|
|
7464
|
+
await ctx.fs.writeFile(K3S_CONFIG_DROPIN, rendered.content, 420);
|
|
7465
|
+
if (!managed) {
|
|
7466
|
+
result.warnings.push(ownerRestartWarning("Kubelet settings changed."));
|
|
7467
|
+
return;
|
|
7468
|
+
}
|
|
7469
|
+
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
7470
|
+
result.warnings.push(
|
|
7471
|
+
`Kubelet settings changed but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
7472
|
+
);
|
|
7473
|
+
return;
|
|
7474
|
+
}
|
|
7475
|
+
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
7476
|
+
if (restart.exitCode === 0) {
|
|
7477
|
+
result.restartedK3s = true;
|
|
7478
|
+
} else {
|
|
7479
|
+
result.warnings.push(
|
|
7480
|
+
`Could not restart k3s to apply new kubelet settings: ${restart.stderr.trim().slice(0, 200)}`
|
|
7481
|
+
);
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
|
|
7485
|
+
// src/daemon/local-registry-upkeep.ts
|
|
7486
|
+
import { createHash as createHash3 } from "crypto";
|
|
7487
|
+
|
|
7488
|
+
// src/lib/local-registry.ts
|
|
7489
|
+
import { createHash as createHash2 } from "crypto";
|
|
7490
|
+
|
|
7491
|
+
// src/lib/registry-credentials.ts
|
|
7492
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
7493
|
+
|
|
7494
|
+
// src/lib/bcrypt.ts
|
|
7495
|
+
var P_WORDS = 18;
|
|
7496
|
+
var S_WORDS = 1024;
|
|
7497
|
+
var MAGIC = "OrpheanBeholderScryDoubt";
|
|
7498
|
+
var ALPHABET = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
7499
|
+
var MAX_KEY_BYTES = 72;
|
|
7500
|
+
var BCRYPT_SALT_BYTES = 16;
|
|
7501
|
+
function scaledAtanInverse(x, one) {
|
|
7502
|
+
const xSquared = x * x;
|
|
7503
|
+
let power = one / x;
|
|
7504
|
+
let sum = 0n;
|
|
7505
|
+
for (let n = 0n; power !== 0n; n++) {
|
|
7506
|
+
const term = power / (2n * n + 1n);
|
|
7507
|
+
sum = n % 2n === 0n ? sum + term : sum - term;
|
|
7508
|
+
power /= xSquared;
|
|
7509
|
+
}
|
|
7510
|
+
return sum;
|
|
7511
|
+
}
|
|
7512
|
+
function piFractionWords(count) {
|
|
7513
|
+
const guard = 64n;
|
|
7514
|
+
const bits = BigInt(count * 32) + guard;
|
|
7515
|
+
const one = 1n << bits;
|
|
7516
|
+
const pi = 16n * scaledAtanInverse(5n, one) - 4n * scaledAtanInverse(239n, one);
|
|
7517
|
+
const fraction = pi - 3n * one >> guard;
|
|
7518
|
+
const words = new Uint32Array(count);
|
|
7519
|
+
for (let index = 0; index < count; index++) {
|
|
7520
|
+
words[index] = Number(fraction >> BigInt(32 * (count - 1 - index)) & 0xffffffffn);
|
|
7521
|
+
}
|
|
7522
|
+
return words;
|
|
7523
|
+
}
|
|
7524
|
+
var initialState = null;
|
|
7525
|
+
function blowfishInitialState() {
|
|
7526
|
+
if (!initialState) {
|
|
7527
|
+
const words = piFractionWords(P_WORDS + S_WORDS);
|
|
7528
|
+
initialState = { p: words.slice(0, P_WORDS), s: words.slice(P_WORDS) };
|
|
7529
|
+
}
|
|
7530
|
+
return initialState;
|
|
7531
|
+
}
|
|
7532
|
+
function word(array, index) {
|
|
7533
|
+
return array[index] ?? 0;
|
|
7534
|
+
}
|
|
7535
|
+
function round(s, x) {
|
|
7536
|
+
const mixed = word(s, x >>> 24) + word(s, 256 + (x >>> 16 & 255)) ^ word(s, 512 + (x >>> 8 & 255));
|
|
7537
|
+
return mixed + word(s, 768 + (x & 255)) | 0;
|
|
7538
|
+
}
|
|
7539
|
+
function encipher(state, data, offset) {
|
|
7540
|
+
const { p, s } = state;
|
|
7541
|
+
let left = word(data, offset) ^ word(p, 0);
|
|
7542
|
+
let right = word(data, offset + 1);
|
|
7543
|
+
for (let index = 1; index <= 16; index += 2) {
|
|
7544
|
+
right ^= round(s, left) ^ word(p, index);
|
|
7545
|
+
left ^= round(s, right) ^ word(p, index + 1);
|
|
7546
|
+
}
|
|
7547
|
+
data[offset] = right ^ word(p, 17);
|
|
7548
|
+
data[offset + 1] = left;
|
|
7549
|
+
}
|
|
7550
|
+
function wordStream(bytes) {
|
|
7551
|
+
let position = 0;
|
|
7552
|
+
return () => {
|
|
7553
|
+
let value = 0;
|
|
7554
|
+
for (let index = 0; index < 4; index++) {
|
|
7555
|
+
value = value << 8 | (bytes[position] ?? 0);
|
|
7556
|
+
position = (position + 1) % bytes.length;
|
|
7557
|
+
}
|
|
7558
|
+
return value >>> 0;
|
|
7249
7559
|
};
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7560
|
+
}
|
|
7561
|
+
function expandKey(state, key, salt) {
|
|
7562
|
+
const nextKeyWord = wordStream(key);
|
|
7563
|
+
for (let index = 0; index < P_WORDS; index++) {
|
|
7564
|
+
state.p[index] = word(state.p, index) ^ nextKeyWord();
|
|
7565
|
+
}
|
|
7566
|
+
const nextSaltWord = salt ? wordStream(salt) : () => 0;
|
|
7567
|
+
const block = new Uint32Array(2);
|
|
7568
|
+
const fill = (target, length) => {
|
|
7569
|
+
for (let index = 0; index < length; index += 2) {
|
|
7570
|
+
block[0] = word(block, 0) ^ nextSaltWord();
|
|
7571
|
+
block[1] = word(block, 1) ^ nextSaltWord();
|
|
7572
|
+
encipher(state, block, 0);
|
|
7573
|
+
target[index] = word(block, 0);
|
|
7574
|
+
target[index + 1] = word(block, 1);
|
|
7254
7575
|
}
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7576
|
+
};
|
|
7577
|
+
fill(state.p, P_WORDS);
|
|
7578
|
+
fill(state.s, S_WORDS);
|
|
7579
|
+
}
|
|
7580
|
+
function encodeBase64(bytes, length) {
|
|
7581
|
+
let out = "";
|
|
7582
|
+
let offset = 0;
|
|
7583
|
+
while (offset < length) {
|
|
7584
|
+
let first = bytes[offset++] ?? 0;
|
|
7585
|
+
out += ALPHABET[first >> 2];
|
|
7586
|
+
first = (first & 3) << 4;
|
|
7587
|
+
if (offset >= length) {
|
|
7588
|
+
out += ALPHABET[first];
|
|
7589
|
+
break;
|
|
7260
7590
|
}
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7591
|
+
let second = bytes[offset++] ?? 0;
|
|
7592
|
+
out += ALPHABET[first | second >> 4];
|
|
7593
|
+
first = (second & 15) << 2;
|
|
7594
|
+
if (offset >= length) {
|
|
7595
|
+
out += ALPHABET[first];
|
|
7596
|
+
break;
|
|
7265
7597
|
}
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7598
|
+
second = bytes[offset++] ?? 0;
|
|
7599
|
+
out += ALPHABET[first | second >> 6];
|
|
7600
|
+
out += ALPHABET[second & 63];
|
|
7601
|
+
}
|
|
7602
|
+
return out;
|
|
7603
|
+
}
|
|
7604
|
+
function bcryptHash(password, cost, salt) {
|
|
7605
|
+
if (!Number.isInteger(cost) || cost < 4 || cost > 31) {
|
|
7606
|
+
throw new Error(`bcrypt cost must be an integer from 4 to 31, not ${cost}`);
|
|
7607
|
+
}
|
|
7608
|
+
if (salt.length !== BCRYPT_SALT_BYTES) {
|
|
7609
|
+
throw new Error(`bcrypt needs a ${BCRYPT_SALT_BYTES}-byte salt`);
|
|
7610
|
+
}
|
|
7611
|
+
const passwordBytes = new TextEncoder().encode(password).slice(0, MAX_KEY_BYTES);
|
|
7612
|
+
const key = new Uint8Array(passwordBytes.length + 1);
|
|
7613
|
+
key.set(passwordBytes);
|
|
7614
|
+
const base = blowfishInitialState();
|
|
7615
|
+
const state = { p: base.p.slice(), s: base.s.slice() };
|
|
7616
|
+
expandKey(state, key, salt);
|
|
7617
|
+
for (let rounds = 2 ** cost; rounds > 0; rounds--) {
|
|
7618
|
+
expandKey(state, key, null);
|
|
7619
|
+
expandKey(state, salt, null);
|
|
7620
|
+
}
|
|
7621
|
+
const magic = new TextEncoder().encode(MAGIC);
|
|
7622
|
+
const nextMagicWord = wordStream(magic);
|
|
7623
|
+
const block = Uint32Array.from({ length: 6 }, () => nextMagicWord());
|
|
7624
|
+
for (let pass = 0; pass < 64; pass++) {
|
|
7625
|
+
for (let offset = 0; offset < 6; offset += 2) encipher(state, block, offset);
|
|
7626
|
+
}
|
|
7627
|
+
const digest = new Uint8Array(24);
|
|
7628
|
+
block.forEach((value, index) => {
|
|
7629
|
+
digest[index * 4] = value >>> 24;
|
|
7630
|
+
digest[index * 4 + 1] = value >>> 16 & 255;
|
|
7631
|
+
digest[index * 4 + 2] = value >>> 8 & 255;
|
|
7632
|
+
digest[index * 4 + 3] = value & 255;
|
|
7633
|
+
});
|
|
7634
|
+
const costText = String(cost).padStart(2, "0");
|
|
7635
|
+
return `$2y$${costText}$${encodeBase64(salt, BCRYPT_SALT_BYTES)}${encodeBase64(digest, 23)}`;
|
|
7269
7636
|
}
|
|
7270
7637
|
|
|
7271
|
-
// src/
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7638
|
+
// src/lib/registry-credentials.ts
|
|
7639
|
+
var LOCAL_REGISTRY_USERNAME = "conveyor";
|
|
7640
|
+
var BCRYPT_COST = 5;
|
|
7641
|
+
var USERNAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
7642
|
+
var PASSWORD = /^[A-Za-z0-9_-]{32,128}$/;
|
|
7643
|
+
function generateRegistryCredentials(random = randomBytes2) {
|
|
7644
|
+
const username = LOCAL_REGISTRY_USERNAME;
|
|
7645
|
+
const password = Buffer.from(random(32)).toString("base64url");
|
|
7646
|
+
const hash = bcryptHash(password, BCRYPT_COST, random(BCRYPT_SALT_BYTES));
|
|
7647
|
+
return { username, password, htpasswd: `${username}:${hash}` };
|
|
7648
|
+
}
|
|
7649
|
+
function parseRegistryCredentials(raw) {
|
|
7650
|
+
if (!raw) return null;
|
|
7651
|
+
try {
|
|
7652
|
+
const value = JSON.parse(raw);
|
|
7653
|
+
const { username, password, htpasswd } = value ?? {};
|
|
7654
|
+
if (typeof username !== "string" || !USERNAME.test(username)) return null;
|
|
7655
|
+
if (typeof password !== "string" || !PASSWORD.test(password)) return null;
|
|
7656
|
+
if (typeof htpasswd !== "string" || !htpasswd.startsWith(`${username}:$2`)) return null;
|
|
7657
|
+
if (htpasswd.includes("\n")) return null;
|
|
7658
|
+
return { username, password, htpasswd };
|
|
7659
|
+
} catch {
|
|
7660
|
+
return null;
|
|
7661
|
+
}
|
|
7277
7662
|
}
|
|
7278
|
-
function
|
|
7279
|
-
|
|
7663
|
+
async function loadOrCreateRegistryCredentials(fs, random) {
|
|
7664
|
+
const stored = parseRegistryCredentials(await fs.readFile(LOCAL_REGISTRY_CREDENTIALS_FILE));
|
|
7665
|
+
if (stored) return stored;
|
|
7666
|
+
const created = generateRegistryCredentials(random);
|
|
7667
|
+
await fs.writeFile(LOCAL_REGISTRY_CREDENTIALS_FILE, `${JSON.stringify(created)}
|
|
7668
|
+
`, 384);
|
|
7669
|
+
return created;
|
|
7280
7670
|
}
|
|
7281
|
-
function
|
|
7282
|
-
return
|
|
7671
|
+
function credentialFingerprint(credentials) {
|
|
7672
|
+
return createHash("sha256").update(`${credentials.username}:${credentials.password}`).digest("hex").slice(0, 16);
|
|
7673
|
+
}
|
|
7674
|
+
function basicAuthorization(credentials) {
|
|
7675
|
+
return `Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64")}`;
|
|
7676
|
+
}
|
|
7677
|
+
function registryDockerConfig(credentials, host) {
|
|
7678
|
+
const auth = Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64");
|
|
7679
|
+
return JSON.stringify({ auths: { [host]: { auth } } });
|
|
7680
|
+
}
|
|
7681
|
+
function parseRegistryAuthState(raw) {
|
|
7682
|
+
if (!raw) return null;
|
|
7683
|
+
try {
|
|
7684
|
+
const value = JSON.parse(raw);
|
|
7685
|
+
if (typeof value?.credential !== "string") return null;
|
|
7686
|
+
const writtenAt = typeof value.writtenAt === "string" && !Number.isNaN(Date.parse(value.writtenAt)) ? value.writtenAt : null;
|
|
7687
|
+
return { credential: value.credential, writtenAt, enforced: value.enforced === true };
|
|
7688
|
+
} catch {
|
|
7689
|
+
return null;
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
async function registryAuthEnforced(readFile3) {
|
|
7693
|
+
return parseRegistryAuthState(await readFile3(LOCAL_REGISTRY_AUTH_STATE_FILE))?.enforced === true;
|
|
7694
|
+
}
|
|
7695
|
+
function withRegistryCredentials(fetchImpl, readFile3) {
|
|
7696
|
+
let credentials = null;
|
|
7697
|
+
return async (url, init) => {
|
|
7698
|
+
credentials ??= readFile3(LOCAL_REGISTRY_CREDENTIALS_FILE).then(parseRegistryCredentials);
|
|
7699
|
+
const stored = await credentials;
|
|
7700
|
+
if (!stored) return fetchImpl(url, init);
|
|
7701
|
+
const headers = new Headers(init?.headers);
|
|
7702
|
+
headers.set("Authorization", basicAuthorization(stored));
|
|
7703
|
+
return fetchImpl(url, { ...init, headers });
|
|
7704
|
+
};
|
|
7283
7705
|
}
|
|
7284
|
-
|
|
7285
|
-
// src/daemon/local-registry-upkeep.ts
|
|
7286
|
-
import { createHash } from "crypto";
|
|
7287
7706
|
|
|
7288
7707
|
// src/lib/local-registry.ts
|
|
7289
7708
|
var LOCAL_REGISTRY_IMAGE = "docker.io/library/registry:3.1.2@sha256:c87f33837722a100572e95d7dc4bf539fc42cf68202b13c3bc03c0ff54c3a649";
|
|
7290
7709
|
var LOCAL_REGISTRY_CONFIG = "/etc/distribution/config.yml";
|
|
7291
7710
|
var LOCAL_REGISTRY_GC_JOB = "conveyor-registry-gc";
|
|
7711
|
+
var LOCAL_REGISTRY_HTPASSWD_SECRET = "conveyor-registry-htpasswd";
|
|
7712
|
+
var HTPASSWD_KEY = "htpasswd";
|
|
7713
|
+
var AUTH_MOUNT = "/etc/distribution/auth";
|
|
7292
7714
|
var LOCAL_REGISTRY_SELECTOR = { "app.kubernetes.io/name": PERSONAL_LOCAL_REGISTRY_NAME };
|
|
7293
7715
|
var MANAGED_BY = { "app.kubernetes.io/managed-by": "conveyor-k3" };
|
|
7716
|
+
var AUTH_CHECKSUM_ANNOTATION = "conveyor.rallycry.com/registry-auth";
|
|
7294
7717
|
var DATA_MOUNT = "/var/lib/registry";
|
|
7295
7718
|
var REGISTRY_ENV = [
|
|
7296
7719
|
{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" },
|
|
@@ -7305,6 +7728,23 @@ var VOLUME_MOUNTS = [
|
|
|
7305
7728
|
{ name: "data", mountPath: DATA_MOUNT },
|
|
7306
7729
|
{ name: "tmp", mountPath: "/tmp" }
|
|
7307
7730
|
];
|
|
7731
|
+
var AUTH_ENV = [
|
|
7732
|
+
{ name: "REGISTRY_AUTH", value: "htpasswd" },
|
|
7733
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_REALM", value: "conveyor" },
|
|
7734
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_PATH", value: `${AUTH_MOUNT}/${HTPASSWD_KEY}` }
|
|
7735
|
+
];
|
|
7736
|
+
var AUTH_VOLUME = {
|
|
7737
|
+
name: "auth",
|
|
7738
|
+
secret: {
|
|
7739
|
+
secretName: LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
7740
|
+
items: [{ key: HTPASSWD_KEY, path: HTPASSWD_KEY }],
|
|
7741
|
+
defaultMode: 256
|
|
7742
|
+
}
|
|
7743
|
+
};
|
|
7744
|
+
var AUTH_VOLUME_MOUNT = { name: "auth", mountPath: AUTH_MOUNT, readOnly: true };
|
|
7745
|
+
function htpasswdChecksum(htpasswd) {
|
|
7746
|
+
return createHash2("sha256").update(htpasswd).digest("hex").slice(0, 16);
|
|
7747
|
+
}
|
|
7308
7748
|
var VOLUMES = [
|
|
7309
7749
|
{ name: "data", hostPath: { path: LOCAL_REGISTRY_DATA_DIR, type: "Directory" } },
|
|
7310
7750
|
{ name: "tmp", emptyDir: { sizeLimit: "64Mi" } }
|
|
@@ -7341,7 +7781,34 @@ function registryNetworkPolicy(metadata) {
|
|
|
7341
7781
|
}
|
|
7342
7782
|
};
|
|
7343
7783
|
}
|
|
7344
|
-
function
|
|
7784
|
+
function renderLocalRegistrySecrets(credentials) {
|
|
7785
|
+
const encode = (value) => Buffer.from(value).toString("base64");
|
|
7786
|
+
const secret = (namespace, name, key, value) => ({
|
|
7787
|
+
apiVersion: "v1",
|
|
7788
|
+
kind: "Secret",
|
|
7789
|
+
metadata: { name, namespace, labels: { ...MANAGED_BY } },
|
|
7790
|
+
type: "Opaque",
|
|
7791
|
+
data: { [key]: encode(value) }
|
|
7792
|
+
});
|
|
7793
|
+
const items = [
|
|
7794
|
+
secret(
|
|
7795
|
+
PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
7796
|
+
LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
7797
|
+
HTPASSWD_KEY,
|
|
7798
|
+
`${credentials.htpasswd}
|
|
7799
|
+
`
|
|
7800
|
+
),
|
|
7801
|
+
secret(
|
|
7802
|
+
PERSONAL_BAKE_NAMESPACE,
|
|
7803
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET,
|
|
7804
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY,
|
|
7805
|
+
registryDockerConfig(credentials, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
7806
|
+
)
|
|
7807
|
+
];
|
|
7808
|
+
return `${JSON.stringify({ apiVersion: "v1", kind: "List", items }, null, 2)}
|
|
7809
|
+
`;
|
|
7810
|
+
}
|
|
7811
|
+
function renderLocalRegistryManifests(auth = null) {
|
|
7345
7812
|
const metadata = {
|
|
7346
7813
|
name: PERSONAL_LOCAL_REGISTRY_NAME,
|
|
7347
7814
|
namespace: PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
@@ -7357,15 +7824,19 @@ function renderLocalRegistryManifests() {
|
|
|
7357
7824
|
strategy: { type: "Recreate" },
|
|
7358
7825
|
selector: { matchLabels: LOCAL_REGISTRY_SELECTOR },
|
|
7359
7826
|
template: {
|
|
7360
|
-
metadata: {
|
|
7827
|
+
metadata: {
|
|
7828
|
+
labels: metadata.labels,
|
|
7829
|
+
...auth ? { annotations: { [AUTH_CHECKSUM_ANNOTATION]: htpasswdChecksum(auth.htpasswd) } } : {}
|
|
7830
|
+
},
|
|
7361
7831
|
spec: {
|
|
7362
7832
|
...POD_BASE,
|
|
7833
|
+
...auth ? { volumes: [...VOLUMES, AUTH_VOLUME] } : {},
|
|
7363
7834
|
containers: [
|
|
7364
7835
|
{
|
|
7365
7836
|
name: "registry",
|
|
7366
7837
|
image: LOCAL_REGISTRY_IMAGE,
|
|
7367
7838
|
imagePullPolicy: "IfNotPresent",
|
|
7368
|
-
env: REGISTRY_ENV,
|
|
7839
|
+
env: auth ? [...REGISTRY_ENV, ...AUTH_ENV] : REGISTRY_ENV,
|
|
7369
7840
|
ports: [
|
|
7370
7841
|
{
|
|
7371
7842
|
name: "registry",
|
|
@@ -7377,8 +7848,10 @@ function renderLocalRegistryManifests() {
|
|
|
7377
7848
|
protocol: "TCP"
|
|
7378
7849
|
}
|
|
7379
7850
|
],
|
|
7851
|
+
// A TCP check: once the credential is enforced, `/v2/` answers
|
|
7852
|
+
// 401 to a probe that does not carry it.
|
|
7380
7853
|
readinessProbe: {
|
|
7381
|
-
|
|
7854
|
+
tcpSocket: { port: PERSONAL_LOCAL_REGISTRY_PORT },
|
|
7382
7855
|
periodSeconds: 10
|
|
7383
7856
|
},
|
|
7384
7857
|
resources: {
|
|
@@ -7386,7 +7859,7 @@ function renderLocalRegistryManifests() {
|
|
|
7386
7859
|
limits: { memory: "512Mi" }
|
|
7387
7860
|
},
|
|
7388
7861
|
securityContext: CONTAINER_SECURITY,
|
|
7389
|
-
volumeMounts: VOLUME_MOUNTS
|
|
7862
|
+
volumeMounts: auth ? [...VOLUME_MOUNTS, AUTH_VOLUME_MOUNT] : VOLUME_MOUNTS
|
|
7390
7863
|
}
|
|
7391
7864
|
]
|
|
7392
7865
|
}
|
|
@@ -7477,11 +7950,18 @@ async function registryAnswers(fetchImpl = fetch) {
|
|
|
7477
7950
|
}
|
|
7478
7951
|
async function localRegistryReady(ports) {
|
|
7479
7952
|
const [answers, mirrored] = await Promise.all([
|
|
7480
|
-
registryAnswers(ports.fetch),
|
|
7953
|
+
registryAnswers(withRegistryCredentials(ports.fetch ?? fetch, ports.readFile)),
|
|
7481
7954
|
mirrorLoaded(ports.readFile)
|
|
7482
7955
|
]);
|
|
7483
7956
|
return answers && mirrored;
|
|
7484
7957
|
}
|
|
7958
|
+
async function localRegistryStatus(ports) {
|
|
7959
|
+
const [ready, authEnforced] = await Promise.all([
|
|
7960
|
+
localRegistryReady(ports),
|
|
7961
|
+
registryAuthEnforced(ports.readFile)
|
|
7962
|
+
]);
|
|
7963
|
+
return { ready, authEnforced };
|
|
7964
|
+
}
|
|
7485
7965
|
|
|
7486
7966
|
// src/daemon/registry-gc-lock.ts
|
|
7487
7967
|
var REGISTRY_GC_MARK = "conveyor.rallycry.com/registry-gc";
|
|
@@ -7572,6 +8052,59 @@ async function deleteSweepJob(exec) {
|
|
|
7572
8052
|
return result.exitCode === 0;
|
|
7573
8053
|
}
|
|
7574
8054
|
|
|
8055
|
+
// src/daemon/registry-auth.ts
|
|
8056
|
+
var LOCAL_REGISTRY_AUTH_HOSTS = [
|
|
8057
|
+
PERSONAL_LOCAL_REGISTRY_HOST,
|
|
8058
|
+
new URL(PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT).host
|
|
8059
|
+
];
|
|
8060
|
+
function registryAuthConfigured(file, credentials) {
|
|
8061
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.every((host) => {
|
|
8062
|
+
const auth = file.configs?.[host]?.auth;
|
|
8063
|
+
return auth?.username === credentials.username && auth.password === credentials.password && auth.auth === void 0 && auth.identitytoken === void 0;
|
|
8064
|
+
});
|
|
8065
|
+
}
|
|
8066
|
+
function withLocalRegistryAuth(file, credentials) {
|
|
8067
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
8068
|
+
(next, host) => upsertRegistryAuth(next, host, {
|
|
8069
|
+
username: credentials.username,
|
|
8070
|
+
password: credentials.password
|
|
8071
|
+
}),
|
|
8072
|
+
file
|
|
8073
|
+
);
|
|
8074
|
+
}
|
|
8075
|
+
function monotonicNowUs() {
|
|
8076
|
+
return Number(process.hrtime.bigint() / 1000n);
|
|
8077
|
+
}
|
|
8078
|
+
async function k3sStartedAtMs(exec, nowMs, monotonicNow = monotonicNowUs) {
|
|
8079
|
+
const result = await exec(
|
|
8080
|
+
"systemctl",
|
|
8081
|
+
["show", "k3s", "--property=ActiveEnterTimestampMonotonic", "--value"],
|
|
8082
|
+
{ timeoutMs: 1e4 }
|
|
8083
|
+
).catch(() => null);
|
|
8084
|
+
if (!result || result.exitCode !== 0) return null;
|
|
8085
|
+
const startedUs = Number.parseInt(result.stdout.trim(), 10);
|
|
8086
|
+
if (!Number.isFinite(startedUs) || startedUs <= 0) return null;
|
|
8087
|
+
return nowMs - (monotonicNow() - startedUs) / 1e3;
|
|
8088
|
+
}
|
|
8089
|
+
async function settleRegistryAuth(ctx, credentials, written) {
|
|
8090
|
+
const now = (ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
8091
|
+
const raw = await ctx.fs.readFile(LOCAL_REGISTRY_AUTH_STATE_FILE);
|
|
8092
|
+
const credential = credentialFingerprint(credentials);
|
|
8093
|
+
const stored = parseRegistryAuthState(raw);
|
|
8094
|
+
const state = stored?.credential === credential ? stored : { credential, writtenAt: null, enforced: false };
|
|
8095
|
+
if (!state.enforced && (written || state.writtenAt === null)) {
|
|
8096
|
+
state.writtenAt = now.toISOString();
|
|
8097
|
+
}
|
|
8098
|
+
if (!state.enforced && state.writtenAt !== null) {
|
|
8099
|
+
const startedAt = await k3sStartedAtMs(ctx.exec, now.getTime(), ctx.monotonicNowUs);
|
|
8100
|
+
if (startedAt !== null && startedAt > Date.parse(state.writtenAt)) state.enforced = true;
|
|
8101
|
+
}
|
|
8102
|
+
const next = `${JSON.stringify(state)}
|
|
8103
|
+
`;
|
|
8104
|
+
if (next !== raw) await ctx.fs.writeFile(LOCAL_REGISTRY_AUTH_STATE_FILE, next, 384);
|
|
8105
|
+
return state.enforced;
|
|
8106
|
+
}
|
|
8107
|
+
|
|
7575
8108
|
// src/daemon/local-registry-upkeep.ts
|
|
7576
8109
|
function nowDate() {
|
|
7577
8110
|
return /* @__PURE__ */ new Date();
|
|
@@ -7580,33 +8113,47 @@ function mirrorConfigured(raw) {
|
|
|
7580
8113
|
const endpoints = mirrorEndpoints(parseRegistries(raw), PERSONAL_LOCAL_REGISTRY_HOST);
|
|
7581
8114
|
return endpoints.length === 1 && endpoints[0] === PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT;
|
|
7582
8115
|
}
|
|
7583
|
-
async function applyUnlessCollecting(ctx) {
|
|
8116
|
+
async function applyUnlessCollecting(ctx, auth) {
|
|
7584
8117
|
const deployment = await readRegistryDeployment(ctx.exec);
|
|
7585
8118
|
if (!deployment) throw new Error("the registry's Deployment could not be read");
|
|
7586
8119
|
if (gcMarkIsLive(deployment.mark, (ctx.now ?? nowDate)().getTime(), ctx.isAlive)) return false;
|
|
7587
8120
|
if (deployment.mark !== null && !await deleteSweepJob(ctx.exec)) {
|
|
7588
8121
|
throw new Error("a stopped cleanup's Job could not be deleted, so the registry stays stopped");
|
|
7589
8122
|
}
|
|
7590
|
-
await kubectlApply(ctx.exec, renderLocalRegistryManifests());
|
|
8123
|
+
await kubectlApply(ctx.exec, renderLocalRegistryManifests(auth));
|
|
7591
8124
|
if (deployment.mark !== null) await clearGcMark(ctx.exec);
|
|
7592
8125
|
return true;
|
|
7593
8126
|
}
|
|
7594
8127
|
async function deployLocalRegistry(ctx) {
|
|
7595
8128
|
await ctx.fs.ensureDir(LOCAL_REGISTRY_DATA_DIR, 448);
|
|
7596
|
-
const
|
|
8129
|
+
const credentials = await loadOrCreateRegistryCredentials(ctx.fs, ctx.random);
|
|
7597
8130
|
const raw = await ctx.fs.readFile(REGISTRIES_FILE);
|
|
7598
|
-
|
|
7599
|
-
const
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
8131
|
+
let file = parseRegistries(raw);
|
|
8132
|
+
const mirrorWritten = !mirrorConfigured(raw);
|
|
8133
|
+
const authWritten = !registryAuthConfigured(file, credentials);
|
|
8134
|
+
let backupPath = null;
|
|
8135
|
+
if (mirrorWritten) {
|
|
8136
|
+
file = upsertRegistryMirror(file, PERSONAL_LOCAL_REGISTRY_HOST, [
|
|
8137
|
+
PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT
|
|
8138
|
+
]);
|
|
8139
|
+
}
|
|
8140
|
+
if (authWritten) file = withLocalRegistryAuth(file, credentials);
|
|
8141
|
+
if (mirrorWritten || authWritten) {
|
|
8142
|
+
backupPath = await writeRegistriesWithBackup(ctx.fs, raw, file, (ctx.now ?? nowDate)());
|
|
8143
|
+
}
|
|
8144
|
+
const authEnforced = await settleRegistryAuth(ctx, credentials, authWritten);
|
|
8145
|
+
await kubectlApply(ctx.exec, renderLocalRegistrySecrets(credentials));
|
|
8146
|
+
const applied = await applyUnlessCollecting(ctx, authEnforced ? credentials : null);
|
|
8147
|
+
return { applied, mirrorWritten, authWritten, backupPath, authEnforced };
|
|
7604
8148
|
}
|
|
7605
8149
|
async function readMirrorState(fs) {
|
|
7606
8150
|
const raw = await fs.readFile(REGISTRIES_FILE);
|
|
7607
|
-
const
|
|
8151
|
+
const authPending = !await registryAuthEnforced((path) => fs.readFile(path));
|
|
8152
|
+
const fingerprint = createHash3("sha256").update(raw ?? "").digest("hex");
|
|
7608
8153
|
if (!mirrorConfigured(raw)) return { state: "absent", fingerprint };
|
|
7609
|
-
if (await mirrorLoaded((path) => fs.readFile(path)))
|
|
8154
|
+
if (!authPending && await mirrorLoaded((path) => fs.readFile(path))) {
|
|
8155
|
+
return { state: "loaded", fingerprint };
|
|
8156
|
+
}
|
|
7610
8157
|
const tried = (await fs.readFile(LOCAL_REGISTRY_MIRROR_RESTART_FILE))?.trim() === fingerprint;
|
|
7611
8158
|
return { state: tried ? "tried" : "owed", fingerprint };
|
|
7612
8159
|
}
|
|
@@ -7618,14 +8165,14 @@ async function loadRegistryMirror(ctx) {
|
|
|
7618
8165
|
if (state === "tried") {
|
|
7619
8166
|
return {
|
|
7620
8167
|
restarted: false,
|
|
7621
|
-
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}.`
|
|
8168
|
+
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}.`
|
|
7622
8169
|
};
|
|
7623
8170
|
}
|
|
7624
8171
|
if (state !== "owed") return { restarted: false, warning: null };
|
|
7625
8172
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
7626
8173
|
return {
|
|
7627
8174
|
restarted: false,
|
|
7628
|
-
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.`
|
|
8175
|
+
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.`
|
|
7629
8176
|
};
|
|
7630
8177
|
}
|
|
7631
8178
|
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
@@ -7640,21 +8187,257 @@ async function loadRegistryMirror(ctx) {
|
|
|
7640
8187
|
return { restarted: true, warning: null };
|
|
7641
8188
|
}
|
|
7642
8189
|
|
|
7643
|
-
// src/
|
|
7644
|
-
var
|
|
7645
|
-
var
|
|
7646
|
-
var
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
8190
|
+
// src/lib/isolation.ts
|
|
8191
|
+
var EGRESS_POLICY_NAME = "conveyor-k3-egress";
|
|
8192
|
+
var MANAGED_BY2 = { "app.kubernetes.io/managed-by": "conveyor-k3" };
|
|
8193
|
+
var NON_ROUTABLE_IPV4 = [
|
|
8194
|
+
"0.0.0.0/8",
|
|
8195
|
+
"10.0.0.0/8",
|
|
8196
|
+
"100.64.0.0/10",
|
|
8197
|
+
"127.0.0.0/8",
|
|
8198
|
+
"169.254.0.0/16",
|
|
8199
|
+
"172.16.0.0/12",
|
|
8200
|
+
"192.0.0.0/24",
|
|
8201
|
+
"192.0.2.0/24",
|
|
8202
|
+
"192.88.99.0/24",
|
|
8203
|
+
"192.168.0.0/16",
|
|
8204
|
+
"198.18.0.0/15",
|
|
8205
|
+
"198.51.100.0/24",
|
|
8206
|
+
"203.0.113.0/24",
|
|
8207
|
+
"224.0.0.0/4",
|
|
8208
|
+
"240.0.0.0/4"
|
|
8209
|
+
];
|
|
8210
|
+
var KUBE_DNS = { namespace: "kube-system", labels: { "k8s-app": "kube-dns" } };
|
|
8211
|
+
var PSA_PREFIX = "pod-security.kubernetes.io";
|
|
8212
|
+
function psaLabels(level) {
|
|
8213
|
+
const advisory = level === "baseline" ? "restricted" : level;
|
|
8214
|
+
return {
|
|
8215
|
+
[`${PSA_PREFIX}/enforce`]: level,
|
|
8216
|
+
[`${PSA_PREFIX}/warn`]: advisory,
|
|
8217
|
+
[`${PSA_PREFIX}/audit`]: advisory
|
|
8218
|
+
};
|
|
7651
8219
|
}
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
8220
|
+
var PSA_LABEL_KEYS = Object.keys(psaLabels("baseline"));
|
|
8221
|
+
function ipv4Value(address) {
|
|
8222
|
+
const parts = address.split(".");
|
|
8223
|
+
if (parts.length !== 4) return null;
|
|
8224
|
+
let value = 0;
|
|
8225
|
+
for (const part of parts) {
|
|
8226
|
+
if (!/^\d{1,3}$/.test(part)) return null;
|
|
8227
|
+
const octet = Number(part);
|
|
8228
|
+
if (octet > 255) return null;
|
|
8229
|
+
value = value * 256 + octet;
|
|
8230
|
+
}
|
|
8231
|
+
return value;
|
|
8232
|
+
}
|
|
8233
|
+
function inNonRoutableRange(address) {
|
|
8234
|
+
const value = ipv4Value(address);
|
|
8235
|
+
if (value === null) return false;
|
|
8236
|
+
return NON_ROUTABLE_IPV4.some((cidr) => {
|
|
8237
|
+
const [base = "", bits = "32"] = cidr.split("/");
|
|
8238
|
+
const start = ipv4Value(base) ?? 0;
|
|
8239
|
+
return value >= start && value < start + 2 ** (32 - Number(bits));
|
|
8240
|
+
});
|
|
8241
|
+
}
|
|
8242
|
+
function hostAddressExceptions(addresses) {
|
|
8243
|
+
const kept = /* @__PURE__ */ new Set();
|
|
8244
|
+
for (const raw of addresses) {
|
|
8245
|
+
const address = raw.trim();
|
|
8246
|
+
if (ipv4Value(address) === null || inNonRoutableRange(address)) continue;
|
|
8247
|
+
kept.add(`${address}/32`);
|
|
8248
|
+
}
|
|
8249
|
+
return [...kept].sort();
|
|
8250
|
+
}
|
|
8251
|
+
function renderEgressNetworkPolicy(namespace, hostAddresses) {
|
|
8252
|
+
const policy = {
|
|
8253
|
+
apiVersion: "networking.k8s.io/v1",
|
|
8254
|
+
kind: "NetworkPolicy",
|
|
8255
|
+
metadata: {
|
|
8256
|
+
name: EGRESS_POLICY_NAME,
|
|
8257
|
+
namespace: assertK8sName(namespace, "namespace"),
|
|
8258
|
+
labels: MANAGED_BY2
|
|
8259
|
+
},
|
|
8260
|
+
spec: {
|
|
8261
|
+
podSelector: {},
|
|
8262
|
+
policyTypes: ["Egress"],
|
|
8263
|
+
egress: [
|
|
8264
|
+
{
|
|
8265
|
+
to: [
|
|
8266
|
+
{
|
|
8267
|
+
namespaceSelector: {
|
|
8268
|
+
matchLabels: { "kubernetes.io/metadata.name": KUBE_DNS.namespace }
|
|
8269
|
+
},
|
|
8270
|
+
podSelector: { matchLabels: KUBE_DNS.labels }
|
|
8271
|
+
}
|
|
8272
|
+
],
|
|
8273
|
+
ports: [
|
|
8274
|
+
{ protocol: "UDP", port: 53 },
|
|
8275
|
+
{ protocol: "TCP", port: 53 }
|
|
8276
|
+
]
|
|
8277
|
+
},
|
|
8278
|
+
{
|
|
8279
|
+
// kube-router enforces after the Service's DNAT, so the registry is
|
|
8280
|
+
// reached by its pods' labels, never by the service range.
|
|
8281
|
+
to: [
|
|
8282
|
+
{
|
|
8283
|
+
namespaceSelector: {
|
|
8284
|
+
matchLabels: { "kubernetes.io/metadata.name": PERSONAL_LOCAL_REGISTRY_NAMESPACE }
|
|
8285
|
+
},
|
|
8286
|
+
podSelector: { matchLabels: LOCAL_REGISTRY_SELECTOR }
|
|
8287
|
+
}
|
|
8288
|
+
],
|
|
8289
|
+
ports: [{ protocol: "TCP", port: PERSONAL_LOCAL_REGISTRY_PORT }]
|
|
8290
|
+
},
|
|
8291
|
+
{
|
|
8292
|
+
to: [
|
|
8293
|
+
{
|
|
8294
|
+
ipBlock: {
|
|
8295
|
+
cidr: "0.0.0.0/0",
|
|
8296
|
+
except: [...NON_ROUTABLE_IPV4, ...hostAddressExceptions(hostAddresses)]
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
]
|
|
8300
|
+
}
|
|
8301
|
+
]
|
|
8302
|
+
}
|
|
8303
|
+
};
|
|
8304
|
+
return `${JSON.stringify(policy, null, 2)}
|
|
8305
|
+
`;
|
|
8306
|
+
}
|
|
8307
|
+
var DISABLE_KEY = /(?:^|[\s{,])(["']?)disable-network-policy\1\s*:([^,}]*)/g;
|
|
8308
|
+
var DISABLE_FLAGS = /* @__PURE__ */ new Set(["--disable-network-policy", "-disable-network-policy"]);
|
|
8309
|
+
var FALSE_VALUES = /* @__PURE__ */ new Set(["false", "no", "off", "n", "f", "0"]);
|
|
8310
|
+
function setsTrue(raw) {
|
|
8311
|
+
const value = bareToken(raw).trim().toLowerCase();
|
|
8312
|
+
return !FALSE_VALUES.has(value);
|
|
8313
|
+
}
|
|
8314
|
+
function uncommented(line) {
|
|
8315
|
+
return /^\s*#/.test(line) ? "" : line.replace(/\s#.*$/, "");
|
|
8316
|
+
}
|
|
8317
|
+
function configSetsKey(text) {
|
|
8318
|
+
return text.split("\n").map(uncommented).some((line) => [...line.matchAll(DISABLE_KEY)].some((match) => setsTrue(match[2] ?? "")));
|
|
8319
|
+
}
|
|
8320
|
+
function unitLines(unitText) {
|
|
8321
|
+
const lines2 = [];
|
|
8322
|
+
let pending = "";
|
|
8323
|
+
for (const raw of unitText.split("\n")) {
|
|
8324
|
+
const line = raw.trimEnd();
|
|
8325
|
+
if (/^\s*[#;]/.test(line)) continue;
|
|
8326
|
+
if (line.endsWith("\\")) {
|
|
8327
|
+
pending += `${line.slice(0, -1)} `;
|
|
8328
|
+
continue;
|
|
8329
|
+
}
|
|
8330
|
+
lines2.push(pending + line);
|
|
8331
|
+
pending = "";
|
|
8332
|
+
}
|
|
8333
|
+
return pending ? [...lines2, pending] : lines2;
|
|
8334
|
+
}
|
|
8335
|
+
function flagSetIn(line) {
|
|
8336
|
+
return line.split(/\s+/).some((raw) => {
|
|
8337
|
+
const token = bareToken(raw).replace(/^["']+|["']+$/g, "");
|
|
8338
|
+
const [name = "", ...value] = token.split("=");
|
|
8339
|
+
return DISABLE_FLAGS.has(name) && (value.length === 0 || setsTrue(value.join("=")));
|
|
8340
|
+
});
|
|
8341
|
+
}
|
|
8342
|
+
function networkPolicyControllerDisabled(sources) {
|
|
8343
|
+
if (sources.configs.some((text) => text !== null && configSetsKey(text))) return true;
|
|
8344
|
+
return unitLines(sources.unitText).some(flagSetIn);
|
|
8345
|
+
}
|
|
8346
|
+
|
|
8347
|
+
// src/daemon/isolation-probes.ts
|
|
8348
|
+
var CONTROLLER_DISABLED_WARNING = "k3s runs with its network policy controller disabled, so the egress policy Conveyor applies here is not enforced. Remove disable-network-policy from the k3s config or unit and restart k3s. Until then this server is not isolated; Conveyor checks again within 10 minutes.";
|
|
8349
|
+
var CONTROLLER_UNKNOWN_WARNING = "Could not read the k3s unit or its config files to check that its network policy controller is on, so this server does not count as isolated. Check `systemctl cat k3s.service` and /etc/rancher/k3s; Conveyor checks again within 10 minutes.";
|
|
8350
|
+
var NODE_ADDRESSES_JSONPATH = '{range .items[*]}{range .status.addresses[?(@.type=="InternalIP")]}{.address}{"\\n"}{end}{range .status.addresses[?(@.type=="ExternalIP")]}{.address}{"\\n"}{end}{end}';
|
|
8351
|
+
var API_ENDPOINTS_JSONPATH = "{.items[*].endpoints[*].addresses[*]}";
|
|
8352
|
+
var IPV4_ON_INTERFACE = /\binet (\d{1,3}(?:\.\d{1,3}){3})(?:\/| peer )/g;
|
|
8353
|
+
async function stdoutOf(exec, bin, args) {
|
|
8354
|
+
const run = await exec(bin, args, { timeoutMs: 1e4 }).catch(() => null);
|
|
8355
|
+
return run && run.exitCode === 0 ? run.stdout : null;
|
|
8356
|
+
}
|
|
8357
|
+
async function dropInDirExists(exec) {
|
|
8358
|
+
const run = await exec("test", ["-d", K3S_CONFIG_DROPIN_DIR], { timeoutMs: 1e4 }).catch(
|
|
8359
|
+
() => null
|
|
8360
|
+
);
|
|
8361
|
+
if (run === null) return null;
|
|
8362
|
+
if (run.exitCode === 0) return true;
|
|
8363
|
+
return run.exitCode === 1 && run.stderr.trim() === "" ? false : null;
|
|
8364
|
+
}
|
|
8365
|
+
async function readK3sConfigs(ctx) {
|
|
8366
|
+
const dirExists = await dropInDirExists(ctx.exec);
|
|
8367
|
+
if (dirExists === null) return null;
|
|
8368
|
+
if (!dirExists) return [await ctx.fs.readFile(K3S_OWNER_CONFIG)];
|
|
8369
|
+
const listed = await stdoutOf(ctx.exec, "find", [
|
|
8370
|
+
"-L",
|
|
8371
|
+
K3S_CONFIG_DROPIN_DIR,
|
|
8372
|
+
"-maxdepth",
|
|
8373
|
+
"1",
|
|
8374
|
+
"-type",
|
|
8375
|
+
"f",
|
|
8376
|
+
"(",
|
|
8377
|
+
"-iname",
|
|
8378
|
+
"*.yaml",
|
|
8379
|
+
"-o",
|
|
8380
|
+
"-iname",
|
|
8381
|
+
"*.yml",
|
|
8382
|
+
")"
|
|
8383
|
+
]);
|
|
8384
|
+
if (listed === null) return null;
|
|
8385
|
+
const paths = listed.split("\n").map((path) => path.trim()).filter(Boolean).sort();
|
|
8386
|
+
const dropIns = await Promise.all(paths.map((path) => ctx.fs.readFile(path)));
|
|
8387
|
+
if (dropIns.some((text) => text === null)) return null;
|
|
8388
|
+
return [await ctx.fs.readFile(K3S_OWNER_CONFIG), ...dropIns];
|
|
8389
|
+
}
|
|
8390
|
+
async function controllerProblem(ctx) {
|
|
8391
|
+
const unitText = await stdoutOf(ctx.exec, "systemctl", ["cat", "k3s.service"]);
|
|
8392
|
+
const configs = unitText === null ? null : await readK3sConfigs(ctx);
|
|
8393
|
+
if (unitText === null || configs === null) return CONTROLLER_UNKNOWN_WARNING;
|
|
8394
|
+
return networkPolicyControllerDisabled({ configs, unitText }) ? CONTROLLER_DISABLED_WARNING : null;
|
|
8395
|
+
}
|
|
8396
|
+
async function readHostAddresses(exec) {
|
|
8397
|
+
const nodes = await stdoutOf(exec, "k3s", [
|
|
8398
|
+
"kubectl",
|
|
8399
|
+
"get",
|
|
8400
|
+
"nodes",
|
|
8401
|
+
"-o",
|
|
8402
|
+
`jsonpath=${NODE_ADDRESSES_JSONPATH}`
|
|
8403
|
+
]);
|
|
8404
|
+
const interfaces = await stdoutOf(exec, "ip", ["-o", "-4", "addr", "show"]);
|
|
8405
|
+
const api = await stdoutOf(exec, "k3s", [
|
|
8406
|
+
"kubectl",
|
|
8407
|
+
"get",
|
|
8408
|
+
"endpointslices",
|
|
8409
|
+
"-n",
|
|
8410
|
+
"default",
|
|
8411
|
+
"-l",
|
|
8412
|
+
"kubernetes.io/service-name=kubernetes",
|
|
8413
|
+
"-o",
|
|
8414
|
+
`jsonpath=${API_ENDPOINTS_JSONPATH}`
|
|
8415
|
+
]);
|
|
8416
|
+
if (nodes === null || interfaces === null || api === null) return null;
|
|
8417
|
+
const onInterfaces = [...interfaces.matchAll(IPV4_ON_INTERFACE)].map((match) => match[1] ?? "");
|
|
8418
|
+
return [...nodes.split(/\s+/), ...onInterfaces, ...api.split(/\s+/)].filter(Boolean);
|
|
8419
|
+
}
|
|
8420
|
+
async function recheckIsolation(ctx, reconciled) {
|
|
8421
|
+
if (reconciled?.applied !== true) return { isolation: reconciled, warnings: [] };
|
|
8422
|
+
const problem = await controllerProblem(ctx);
|
|
8423
|
+
return problem ? { isolation: { applied: false }, warnings: [problem] } : { isolation: reconciled, warnings: [] };
|
|
8424
|
+
}
|
|
8425
|
+
|
|
8426
|
+
// src/daemon/prune-namespaces.ts
|
|
8427
|
+
var POOL_NAMESPACE_PREFIX = "claudespace-";
|
|
8428
|
+
var WORKSPACE_MANAGER_ROLE = "conveyor-workspace-manager";
|
|
8429
|
+
var MAX_NAMESPACE_DELETES_PER_RUN = 5;
|
|
8430
|
+
var KUBECTL_TIMEOUT = "--request-timeout=10s";
|
|
8431
|
+
var EXEC_OPTIONS = { timeoutMs: 15e3 };
|
|
8432
|
+
function kubectl(exec, args) {
|
|
8433
|
+
return exec("k3s", ["kubectl", ...args, KUBECTL_TIMEOUT], EXEC_OPTIONS).catch(() => null);
|
|
8434
|
+
}
|
|
8435
|
+
function candidateNamespaces(listing, wanted) {
|
|
8436
|
+
const names = [];
|
|
8437
|
+
for (const line of listing.split("\n")) {
|
|
8438
|
+
const [name, phase] = line.trim().split(/\s+/);
|
|
8439
|
+
if (!name?.startsWith(POOL_NAMESPACE_PREFIX) || wanted.has(name)) continue;
|
|
8440
|
+
if (phase === "Terminating") continue;
|
|
7658
8441
|
try {
|
|
7659
8442
|
names.push(assertK8sName(name, "namespace"));
|
|
7660
8443
|
} catch {
|
|
@@ -7702,6 +8485,192 @@ async function pruneStaleNamespaces(ctx, desired, result) {
|
|
|
7702
8485
|
}
|
|
7703
8486
|
}
|
|
7704
8487
|
|
|
8488
|
+
// src/daemon/namespace-upkeep.ts
|
|
8489
|
+
function message(error) {
|
|
8490
|
+
return error instanceof Error ? error.message : String(error);
|
|
8491
|
+
}
|
|
8492
|
+
function renderLimitRange(namespace, limits) {
|
|
8493
|
+
const ns = assertK8sName(namespace, "namespace");
|
|
8494
|
+
const values = [
|
|
8495
|
+
assertQuantity(limits.defaultRequest.cpu, "defaultRequest.cpu"),
|
|
8496
|
+
assertQuantity(limits.defaultRequest.memory, "defaultRequest.memory"),
|
|
8497
|
+
assertQuantity(limits.default.cpu, "default.cpu"),
|
|
8498
|
+
assertQuantity(limits.default.memory, "default.memory")
|
|
8499
|
+
];
|
|
8500
|
+
return [
|
|
8501
|
+
"apiVersion: v1",
|
|
8502
|
+
"kind: LimitRange",
|
|
8503
|
+
"metadata:",
|
|
8504
|
+
" name: conveyor-k3-defaults",
|
|
8505
|
+
` namespace: ${ns}`,
|
|
8506
|
+
"spec:",
|
|
8507
|
+
" limits:",
|
|
8508
|
+
" - type: Container",
|
|
8509
|
+
" defaultRequest:",
|
|
8510
|
+
` cpu: "${values[0]}"`,
|
|
8511
|
+
` memory: "${values[1]}"`,
|
|
8512
|
+
" default:",
|
|
8513
|
+
` cpu: "${values[2]}"`,
|
|
8514
|
+
` memory: "${values[3]}"`,
|
|
8515
|
+
""
|
|
8516
|
+
].join("\n");
|
|
8517
|
+
}
|
|
8518
|
+
async function reconcileNamespaces(ctx, desired, result) {
|
|
8519
|
+
for (const namespace of desired.namespaces) {
|
|
8520
|
+
try {
|
|
8521
|
+
await kubectlApply(ctx.exec, renderLimitRange(namespace.name, namespace.limitRange));
|
|
8522
|
+
} catch (error) {
|
|
8523
|
+
result.warnings.push(
|
|
8524
|
+
`Could not apply resource defaults to ${namespace.name} (${namespace.projectName}): ${message(error)}`
|
|
8525
|
+
);
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
}
|
|
8529
|
+
async function ensureBakeNamespace(ctx, desired) {
|
|
8530
|
+
const found = await ctx.exec(
|
|
8531
|
+
"k3s",
|
|
8532
|
+
["kubectl", "get", "namespace", PERSONAL_BAKE_NAMESPACE],
|
|
8533
|
+
KUBECTL_TIMEOUT2
|
|
8534
|
+
);
|
|
8535
|
+
if (found.exitCode !== 0) {
|
|
8536
|
+
await runOrThrow(
|
|
8537
|
+
ctx.exec,
|
|
8538
|
+
"k3s",
|
|
8539
|
+
["kubectl", "create", "namespace", PERSONAL_BAKE_NAMESPACE],
|
|
8540
|
+
KUBECTL_TIMEOUT2
|
|
8541
|
+
);
|
|
8542
|
+
}
|
|
8543
|
+
const limitRange = desired.namespaces[0]?.limitRange;
|
|
8544
|
+
if (limitRange) {
|
|
8545
|
+
await kubectlApply(ctx.exec, renderLimitRange(PERSONAL_BAKE_NAMESPACE, limitRange));
|
|
8546
|
+
}
|
|
8547
|
+
}
|
|
8548
|
+
function knownStance(isolation) {
|
|
8549
|
+
const { podSecurity, egress, ...rest } = { ...isolation };
|
|
8550
|
+
const extra = Object.keys(rest);
|
|
8551
|
+
if (podSecurity === "baseline" && egress === "no-private" && extra.length === 0) return null;
|
|
8552
|
+
const named = `Pod Security "${String(podSecurity).slice(0, 40)}", egress "${String(egress).slice(0, 40)}"`;
|
|
8553
|
+
return extra.length === 0 ? named : `${named}, and ${extra.slice(0, 3).join(", ").slice(0, 80)}`;
|
|
8554
|
+
}
|
|
8555
|
+
var KUBECTL_TIMEOUT2 = { timeoutMs: 3e4 };
|
|
8556
|
+
var RECHECK = "Conveyor checks again within 10 minutes.";
|
|
8557
|
+
async function isolateNamespace(ctx, namespace, isolation, fail) {
|
|
8558
|
+
const ns = assertK8sName(namespace, "namespace");
|
|
8559
|
+
const labels = Object.entries(psaLabels(isolation.podSecurity)).map(
|
|
8560
|
+
([key, value]) => `${key}=${value}`
|
|
8561
|
+
);
|
|
8562
|
+
const labelled = await ctx.exec(
|
|
8563
|
+
"k3s",
|
|
8564
|
+
["kubectl", "label", "namespace", ns, "--overwrite", ...labels],
|
|
8565
|
+
KUBECTL_TIMEOUT2
|
|
8566
|
+
);
|
|
8567
|
+
if (labelled.exitCode !== 0) {
|
|
8568
|
+
fail(
|
|
8569
|
+
`Could not apply Pod Security labels to ${ns}: ${labelled.stderr.trim().slice(0, 200)} ${RECHECK}`
|
|
8570
|
+
);
|
|
8571
|
+
}
|
|
8572
|
+
if (isolation.hostAddresses === null) return;
|
|
8573
|
+
try {
|
|
8574
|
+
await kubectlApply(ctx.exec, renderEgressNetworkPolicy(ns, isolation.hostAddresses));
|
|
8575
|
+
} catch (error) {
|
|
8576
|
+
fail(`Could not apply the egress policy to ${ns}: ${message(error).slice(0, 200)} ${RECHECK}`);
|
|
8577
|
+
}
|
|
8578
|
+
}
|
|
8579
|
+
async function reconcileIsolation(ctx, desired, result) {
|
|
8580
|
+
try {
|
|
8581
|
+
await ensureBakeNamespace(ctx, desired);
|
|
8582
|
+
} catch (error) {
|
|
8583
|
+
result.warnings.push(
|
|
8584
|
+
`Could not prepare the bake namespace ${PERSONAL_BAKE_NAMESPACE}: ${message(error)}`
|
|
8585
|
+
);
|
|
8586
|
+
}
|
|
8587
|
+
const isolation = desired.isolation;
|
|
8588
|
+
if (!isolation) {
|
|
8589
|
+
result.isolation = void 0;
|
|
8590
|
+
return;
|
|
8591
|
+
}
|
|
8592
|
+
const unknown = knownStance(isolation);
|
|
8593
|
+
if (unknown) {
|
|
8594
|
+
result.warnings.push(
|
|
8595
|
+
`Conveyor asks for pod isolation this version of conveyor-k3 does not know (${unknown}), so nothing was changed and this server does not count as isolated. Run \`sudo conveyor-k3 update\`.`
|
|
8596
|
+
);
|
|
8597
|
+
result.isolation = { applied: false };
|
|
8598
|
+
return;
|
|
8599
|
+
}
|
|
8600
|
+
let applied = true;
|
|
8601
|
+
const fail = (warning) => {
|
|
8602
|
+
applied = false;
|
|
8603
|
+
result.warnings.push(warning);
|
|
8604
|
+
};
|
|
8605
|
+
const problem = await controllerProblem(ctx);
|
|
8606
|
+
if (problem) fail(problem);
|
|
8607
|
+
const hostAddresses = await readHostAddresses(ctx.exec);
|
|
8608
|
+
if (hostAddresses === null) {
|
|
8609
|
+
fail(
|
|
8610
|
+
`Could not read every address this server answers on, so the egress policies in place were left as they are and this server does not count as isolated. ${RECHECK}`
|
|
8611
|
+
);
|
|
8612
|
+
}
|
|
8613
|
+
for (const namespace of desired.namespaces) {
|
|
8614
|
+
await isolateNamespace(ctx, namespace.name, { podSecurity: "baseline", hostAddresses }, fail);
|
|
8615
|
+
}
|
|
8616
|
+
await isolateNamespace(
|
|
8617
|
+
ctx,
|
|
8618
|
+
PERSONAL_BAKE_NAMESPACE,
|
|
8619
|
+
{ podSecurity: "privileged", hostAddresses },
|
|
8620
|
+
fail
|
|
8621
|
+
);
|
|
8622
|
+
result.isolation = { applied };
|
|
8623
|
+
}
|
|
8624
|
+
|
|
8625
|
+
// src/lib/fs-port.ts
|
|
8626
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
8627
|
+
var nodeFileSystem = {
|
|
8628
|
+
async readFile(path) {
|
|
8629
|
+
try {
|
|
8630
|
+
return await readFile(path, "utf8");
|
|
8631
|
+
} catch {
|
|
8632
|
+
return null;
|
|
8633
|
+
}
|
|
8634
|
+
},
|
|
8635
|
+
async writeFile(path, content, mode) {
|
|
8636
|
+
await mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true });
|
|
8637
|
+
await writeFile(path, content, { encoding: "utf8", ...mode === void 0 ? {} : { mode } });
|
|
8638
|
+
if (mode !== void 0) await chmod(path, mode);
|
|
8639
|
+
},
|
|
8640
|
+
async removeFile(path) {
|
|
8641
|
+
await rm(path, { force: true });
|
|
8642
|
+
},
|
|
8643
|
+
async ensureDir(path, mode) {
|
|
8644
|
+
await mkdir(path, { recursive: true });
|
|
8645
|
+
if (mode !== void 0) await chmod(path, mode);
|
|
8646
|
+
}
|
|
8647
|
+
};
|
|
8648
|
+
function createMemoryFileSystem(seed = {}) {
|
|
8649
|
+
const files = new Map(Object.entries(seed));
|
|
8650
|
+
const modes = /* @__PURE__ */ new Map();
|
|
8651
|
+
const removed = [];
|
|
8652
|
+
return {
|
|
8653
|
+
files,
|
|
8654
|
+
modes,
|
|
8655
|
+
removed,
|
|
8656
|
+
readFile: (path) => Promise.resolve(files.get(path) ?? null),
|
|
8657
|
+
writeFile: (path, content, mode) => {
|
|
8658
|
+
files.set(path, content);
|
|
8659
|
+
if (mode !== void 0) modes.set(path, mode);
|
|
8660
|
+
return Promise.resolve();
|
|
8661
|
+
},
|
|
8662
|
+
removeFile: (path) => {
|
|
8663
|
+
files.delete(path);
|
|
8664
|
+
removed.push(path);
|
|
8665
|
+
return Promise.resolve();
|
|
8666
|
+
},
|
|
8667
|
+
ensureDir: (path, mode) => {
|
|
8668
|
+
if (mode !== void 0) modes.set(path, mode);
|
|
8669
|
+
return Promise.resolve();
|
|
8670
|
+
}
|
|
8671
|
+
};
|
|
8672
|
+
}
|
|
8673
|
+
|
|
7705
8674
|
// src/daemon/registry-tags.ts
|
|
7706
8675
|
var REPO_NAME = /^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
|
|
7707
8676
|
var TAG_NAME = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
|
|
@@ -7987,7 +8956,10 @@ async function collectRegistryGarbage(ports, options) {
|
|
|
7987
8956
|
blobsCollected: false
|
|
7988
8957
|
});
|
|
7989
8958
|
const now = ports.now ?? Date.now;
|
|
7990
|
-
const fetchImpl =
|
|
8959
|
+
const fetchImpl = withRegistryCredentials(
|
|
8960
|
+
ports.fetch ?? fetch,
|
|
8961
|
+
ports.readFile ?? ((path) => nodeFileSystem.readFile(path))
|
|
8962
|
+
);
|
|
7991
8963
|
const retain = await readRetainList(ports);
|
|
7992
8964
|
if (typeof retain === "string") return skip(retain);
|
|
7993
8965
|
const blocked = await registryBlocker(ports, now());
|
|
@@ -8153,17 +9125,26 @@ var GRANTED_COMMAND_NAMES = LOCAL_BAKE_ROOT_COMMANDS.map((command) => command.na
|
|
|
8153
9125
|
|
|
8154
9126
|
// src/daemon/telemetry.ts
|
|
8155
9127
|
var LOCAL_REGISTRY_WARN_AFTER_MS = 10 * 6e4;
|
|
9128
|
+
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.";
|
|
8156
9129
|
async function reportLocalRegistry(ports, watch, now = Date.now()) {
|
|
8157
9130
|
if (!watch) return { fields: {}, warnings: [] };
|
|
8158
|
-
const ready = await
|
|
9131
|
+
const { ready, authEnforced } = await localRegistryStatus({
|
|
9132
|
+
readFile: ports.readFile,
|
|
9133
|
+
fetch: ports.fetch
|
|
9134
|
+
});
|
|
8159
9135
|
const paused = (watch.pausedUntil ?? 0) > now;
|
|
8160
9136
|
watch.notReadySince = ready || paused ? null : watch.notReadySince ?? now;
|
|
8161
9137
|
const stuckMs = watch.notReadySince === null ? 0 : now - watch.notReadySince;
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
warnings
|
|
9138
|
+
const warnings = [];
|
|
9139
|
+
if (stuckMs >= LOCAL_REGISTRY_WARN_AFTER_MS) {
|
|
9140
|
+
warnings.push(
|
|
8165
9141
|
`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.`
|
|
8166
|
-
|
|
9142
|
+
);
|
|
9143
|
+
}
|
|
9144
|
+
if (!authEnforced) warnings.push(LOCAL_REGISTRY_OPEN_WARNING);
|
|
9145
|
+
return {
|
|
9146
|
+
fields: { localRegistry: { host: PERSONAL_LOCAL_REGISTRY_HOST, ready, authEnforced } },
|
|
9147
|
+
warnings
|
|
8167
9148
|
};
|
|
8168
9149
|
}
|
|
8169
9150
|
var VM_CAPACITY_FILE = "/etc/conveyor-vm/capacity.json";
|
|
@@ -8285,14 +9266,23 @@ async function collectHeartbeat(ports, state, inventory) {
|
|
|
8285
9266
|
...runnerUsers.length > 0 ? { runnerUsers } : {},
|
|
8286
9267
|
...localImages ? { localImages } : {},
|
|
8287
9268
|
...registry.fields,
|
|
8288
|
-
|
|
9269
|
+
...state.isolation ? { isolation: state.isolation } : {},
|
|
9270
|
+
...state.cpuQuotaApplied === void 0 ? {} : { cpuQuotaApplied: state.cpuQuotaApplied },
|
|
9271
|
+
localWarnings: withinHeartbeatBounds([
|
|
8289
9272
|
...state.localWarnings,
|
|
8290
9273
|
...gcWarning ? [gcWarning] : [],
|
|
8291
9274
|
...ungranted.length > 0 ? [ungrantedRunnersWarning(ungranted)] : [],
|
|
8292
9275
|
...registry.warnings
|
|
8293
|
-
]
|
|
9276
|
+
])
|
|
8294
9277
|
};
|
|
8295
9278
|
}
|
|
9279
|
+
var HEARTBEAT_LIST_MAX = 64;
|
|
9280
|
+
var HEARTBEAT_STRING_MAX = 512;
|
|
9281
|
+
function withinHeartbeatBounds(warnings) {
|
|
9282
|
+
return warnings.slice(0, HEARTBEAT_LIST_MAX).map(
|
|
9283
|
+
(warning) => warning.length > HEARTBEAT_STRING_MAX ? `${warning.slice(0, HEARTBEAT_STRING_MAX - 1)}\u2026` : warning
|
|
9284
|
+
);
|
|
9285
|
+
}
|
|
8296
9286
|
async function readPodIdentities(exec) {
|
|
8297
9287
|
const result = await exec(
|
|
8298
9288
|
"k3s",
|
|
@@ -8328,7 +9318,15 @@ async function collect(ports, now) {
|
|
|
8328
9318
|
if (watch) watch.pausedUntil = now.getTime() + REGISTRY_GC_MARK_TTL_MS;
|
|
8329
9319
|
try {
|
|
8330
9320
|
const outcome = await collectRegistryGarbage(
|
|
8331
|
-
{
|
|
9321
|
+
{
|
|
9322
|
+
exec,
|
|
9323
|
+
log,
|
|
9324
|
+
requestDesiredState,
|
|
9325
|
+
fetch: fetch2,
|
|
9326
|
+
readFile: (path) => ports.fs.readFile(path),
|
|
9327
|
+
sleep: sleep2,
|
|
9328
|
+
isAlive
|
|
9329
|
+
},
|
|
8332
9330
|
{ dryRun: false }
|
|
8333
9331
|
);
|
|
8334
9332
|
if (outcome.skipped) return outcome.skipped;
|
|
@@ -8374,41 +9372,7 @@ async function runRegistryUpkeep(ports, lastGcAt, maintenanceHour) {
|
|
|
8374
9372
|
}
|
|
8375
9373
|
|
|
8376
9374
|
// src/daemon/reconcile.ts
|
|
8377
|
-
async function
|
|
8378
|
-
const ownerConfig = await ctx.fs.readFile(K3S_OWNER_CONFIG);
|
|
8379
|
-
const rendered = renderK3sDropIn(desired.kubelet, ownerConfig);
|
|
8380
|
-
result.skippedKubeletFlags = rendered.skipped;
|
|
8381
|
-
result.effectiveImageGcHighThreshold = rendered.skipped.includes("image-gc-high-threshold") ? ownerImageGcHighThreshold(ownerConfig) : desired.kubelet.imageGcHighThreshold;
|
|
8382
|
-
for (const flag of rendered.skipped) {
|
|
8383
|
-
result.warnings.push(
|
|
8384
|
-
`Left \`${flag}\` to your own /etc/rancher/k3s/config.yaml \u2014 Conveyor does not override a setting you chose.`
|
|
8385
|
-
);
|
|
8386
|
-
}
|
|
8387
|
-
const current = await ctx.fs.readFile(K3S_CONFIG_DROPIN);
|
|
8388
|
-
if (rendered.content === null) {
|
|
8389
|
-
if (current !== null) await ctx.fs.removeFile(K3S_CONFIG_DROPIN);
|
|
8390
|
-
return;
|
|
8391
|
-
}
|
|
8392
|
-
if (current === rendered.content) return;
|
|
8393
|
-
await ctx.fs.ensureDir(K3S_CONFIG_DROPIN.slice(0, K3S_CONFIG_DROPIN.lastIndexOf("/")));
|
|
8394
|
-
await ctx.fs.writeFile(K3S_CONFIG_DROPIN, rendered.content, 420);
|
|
8395
|
-
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
8396
|
-
result.warnings.push(
|
|
8397
|
-
`Kubelet settings changed but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
8398
|
-
);
|
|
8399
|
-
return;
|
|
8400
|
-
}
|
|
8401
|
-
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
8402
|
-
if (restart.exitCode === 0) {
|
|
8403
|
-
result.restartedK3s = true;
|
|
8404
|
-
} else {
|
|
8405
|
-
result.warnings.push(
|
|
8406
|
-
`Could not restart k3s to apply new kubelet settings: ${restart.stderr.trim().slice(0, 200)}`
|
|
8407
|
-
);
|
|
8408
|
-
}
|
|
8409
|
-
}
|
|
8410
|
-
async function reconcilePriorityClass(ctx, desired, result) {
|
|
8411
|
-
const name = assertK8sName(desired.priorityClass.name, "priorityClass.name");
|
|
9375
|
+
async function livePriorityClassValue(ctx, name) {
|
|
8412
9376
|
const read = await ctx.exec("k3s", [
|
|
8413
9377
|
"kubectl",
|
|
8414
9378
|
"get",
|
|
@@ -8417,67 +9381,59 @@ async function reconcilePriorityClass(ctx, desired, result) {
|
|
|
8417
9381
|
"-o",
|
|
8418
9382
|
"jsonpath={.value}"
|
|
8419
9383
|
]);
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
9384
|
+
const value = Number.parseInt(read.stdout.trim(), 10);
|
|
9385
|
+
return read.exitCode === 0 && Number.isFinite(value) ? value : null;
|
|
9386
|
+
}
|
|
9387
|
+
async function reconcilePriorityClass(ctx, desired, result) {
|
|
9388
|
+
const name = assertK8sName(desired.priorityClass.name, "priorityClass.name");
|
|
9389
|
+
const wanted = desired.priorityClass.value;
|
|
9390
|
+
const existingValue = await livePriorityClassValue(ctx, name);
|
|
9391
|
+
if (existingValue === null || existingValue === wanted) return "ready";
|
|
9392
|
+
if (!declaresPriorityClass(desired.rbacManifest, name, wanted)) {
|
|
9393
|
+
result.warnings.push(
|
|
9394
|
+
`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.`
|
|
9395
|
+
);
|
|
9396
|
+
return "kept";
|
|
9397
|
+
}
|
|
8423
9398
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
8424
9399
|
result.warnings.push(
|
|
8425
|
-
`The \`${name}\` PriorityClass has value ${existingValue}, but Conveyor needs ${
|
|
9400
|
+
`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)}.`
|
|
8426
9401
|
);
|
|
8427
|
-
return;
|
|
9402
|
+
return "kept";
|
|
8428
9403
|
}
|
|
8429
|
-
const removed = await ctx.exec("k3s", [
|
|
9404
|
+
const removed = await ctx.exec("k3s", [
|
|
9405
|
+
"kubectl",
|
|
9406
|
+
"delete",
|
|
9407
|
+
"priorityclass",
|
|
9408
|
+
name,
|
|
9409
|
+
"--ignore-not-found"
|
|
9410
|
+
]);
|
|
8430
9411
|
if (removed.exitCode !== 0) {
|
|
8431
9412
|
result.warnings.push(
|
|
8432
|
-
`Could not replace the \`${name}\` PriorityClass (value ${existingValue}, needs ${
|
|
9413
|
+
`Could not replace the \`${name}\` PriorityClass (value ${existingValue}, needs ${wanted}): ${removed.stderr.trim().slice(0, 160)}`
|
|
8433
9414
|
);
|
|
8434
|
-
return;
|
|
9415
|
+
return "kept";
|
|
8435
9416
|
}
|
|
9417
|
+
return "removed";
|
|
9418
|
+
}
|
|
9419
|
+
async function applyRbac(ctx, desired, result) {
|
|
9420
|
+
const outcome = await reconcilePriorityClass(ctx, desired, result);
|
|
9421
|
+
const manifest = outcome === "kept" ? withoutKind(desired.rbacManifest, "PriorityClass") : desired.rbacManifest;
|
|
8436
9422
|
try {
|
|
8437
|
-
await kubectlApply(ctx.exec,
|
|
8438
|
-
result.recreatedPriorityClass = true;
|
|
9423
|
+
await kubectlApply(ctx.exec, manifest);
|
|
8439
9424
|
} catch (error) {
|
|
9425
|
+
if (outcome !== "removed") throw error;
|
|
9426
|
+
const value = await livePriorityClassValue(ctx, desired.priorityClass.name).catch(() => null);
|
|
9427
|
+
if (value === desired.priorityClass.value) {
|
|
9428
|
+
result.recreatedPriorityClass = true;
|
|
9429
|
+
throw error;
|
|
9430
|
+
}
|
|
8440
9431
|
result.warnings.push(
|
|
8441
|
-
`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\`.`
|
|
9432
|
+
`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\`.`
|
|
8442
9433
|
);
|
|
9434
|
+
return;
|
|
8443
9435
|
}
|
|
8444
|
-
|
|
8445
|
-
function renderLimitRange(namespace, limits) {
|
|
8446
|
-
const ns = assertK8sName(namespace, "namespace");
|
|
8447
|
-
const values = [
|
|
8448
|
-
assertQuantity(limits.defaultRequest.cpu, "defaultRequest.cpu"),
|
|
8449
|
-
assertQuantity(limits.defaultRequest.memory, "defaultRequest.memory"),
|
|
8450
|
-
assertQuantity(limits.default.cpu, "default.cpu"),
|
|
8451
|
-
assertQuantity(limits.default.memory, "default.memory")
|
|
8452
|
-
];
|
|
8453
|
-
return [
|
|
8454
|
-
"apiVersion: v1",
|
|
8455
|
-
"kind: LimitRange",
|
|
8456
|
-
"metadata:",
|
|
8457
|
-
" name: conveyor-k3-defaults",
|
|
8458
|
-
` namespace: ${ns}`,
|
|
8459
|
-
"spec:",
|
|
8460
|
-
" limits:",
|
|
8461
|
-
" - type: Container",
|
|
8462
|
-
" defaultRequest:",
|
|
8463
|
-
` cpu: "${values[0]}"`,
|
|
8464
|
-
` memory: "${values[1]}"`,
|
|
8465
|
-
" default:",
|
|
8466
|
-
` cpu: "${values[2]}"`,
|
|
8467
|
-
` memory: "${values[3]}"`,
|
|
8468
|
-
""
|
|
8469
|
-
].join("\n");
|
|
8470
|
-
}
|
|
8471
|
-
async function reconcileNamespaces(ctx, desired, result) {
|
|
8472
|
-
for (const namespace of desired.namespaces) {
|
|
8473
|
-
try {
|
|
8474
|
-
await kubectlApply(ctx.exec, renderLimitRange(namespace.name, namespace.limitRange));
|
|
8475
|
-
} catch (error) {
|
|
8476
|
-
result.warnings.push(
|
|
8477
|
-
`Could not apply resource defaults to ${namespace.name} (${namespace.projectName}): ${error instanceof Error ? error.message : String(error)}`
|
|
8478
|
-
);
|
|
8479
|
-
}
|
|
8480
|
-
}
|
|
9436
|
+
if (outcome === "removed") result.recreatedPriorityClass = true;
|
|
8481
9437
|
}
|
|
8482
9438
|
async function step(result, label, run) {
|
|
8483
9439
|
try {
|
|
@@ -8503,18 +9459,19 @@ async function reconcile(ctx, desired) {
|
|
|
8503
9459
|
`Conveyor sent a cluster manifest containing disallowed kind(s) ${disallowedKinds.join(", ")} \u2014 refused. RBAC and PriorityClass steps skipped; this should never happen, report it.`
|
|
8504
9460
|
);
|
|
8505
9461
|
} else {
|
|
8506
|
-
await step(result, "Could not apply RBAC", () =>
|
|
8507
|
-
await step(
|
|
8508
|
-
result,
|
|
8509
|
-
"Could not reconcile the PriorityClass",
|
|
8510
|
-
() => reconcilePriorityClass(ctx, desired, result)
|
|
8511
|
-
);
|
|
9462
|
+
await step(result, "Could not apply RBAC", () => applyRbac(ctx, desired, result));
|
|
8512
9463
|
}
|
|
8513
9464
|
await step(
|
|
8514
9465
|
result,
|
|
8515
9466
|
"Could not apply namespace defaults",
|
|
8516
9467
|
() => reconcileNamespaces(ctx, desired, result)
|
|
8517
9468
|
);
|
|
9469
|
+
await step(
|
|
9470
|
+
result,
|
|
9471
|
+
"Could not apply pod isolation",
|
|
9472
|
+
() => reconcileIsolation(ctx, desired, result)
|
|
9473
|
+
);
|
|
9474
|
+
if (desired.isolation && result.isolation === void 0) result.isolation = { applied: false };
|
|
8518
9475
|
if (manifestAccepted) {
|
|
8519
9476
|
await step(
|
|
8520
9477
|
result,
|
|
@@ -8523,6 +9480,7 @@ async function reconcile(ctx, desired) {
|
|
|
8523
9480
|
);
|
|
8524
9481
|
}
|
|
8525
9482
|
const registry = desired.localRegistry?.enabled === true;
|
|
9483
|
+
const managed = managesK3s(desired, await readState(ctx.fs).catch(() => ({})));
|
|
8526
9484
|
if (registry) {
|
|
8527
9485
|
await step(result, "Could not reconcile the local image registry", async () => {
|
|
8528
9486
|
await deployLocalRegistry(ctx);
|
|
@@ -8531,10 +9489,27 @@ async function reconcile(ctx, desired) {
|
|
|
8531
9489
|
await step(
|
|
8532
9490
|
result,
|
|
8533
9491
|
"Could not apply kubelet settings",
|
|
8534
|
-
() => reconcileKubelet(ctx, desired, result)
|
|
9492
|
+
() => reconcileKubelet(ctx, desired, result, managed)
|
|
8535
9493
|
);
|
|
9494
|
+
if (desired.isolation !== void 0) {
|
|
9495
|
+
await step(result, "Could not cap pod CPU", async () => {
|
|
9496
|
+
result.cpuQuotaApplied = await reconcilePodCpuQuota(ctx.exec, desired, result.warnings);
|
|
9497
|
+
});
|
|
9498
|
+
if (desired.kubelet.systemReserved?.cpu && result.cpuQuotaApplied === void 0) {
|
|
9499
|
+
result.cpuQuotaApplied = false;
|
|
9500
|
+
}
|
|
9501
|
+
}
|
|
8536
9502
|
if (registry && !result.restartedK3s) {
|
|
8537
9503
|
await step(result, "Could not load the local image registry mirror", async () => {
|
|
9504
|
+
if (!managed) {
|
|
9505
|
+
if (await registryMirrorPending(ctx.fs))
|
|
9506
|
+
result.warnings.push(
|
|
9507
|
+
ownerRestartWarning(
|
|
9508
|
+
"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."
|
|
9509
|
+
)
|
|
9510
|
+
);
|
|
9511
|
+
return;
|
|
9512
|
+
}
|
|
8538
9513
|
const loaded = await loadRegistryMirror(ctx);
|
|
8539
9514
|
result.restartedK3s = loaded.restarted;
|
|
8540
9515
|
if (loaded.warning) result.warnings.push(loaded.warning);
|
|
@@ -8547,6 +9522,8 @@ async function reconcile(ctx, desired) {
|
|
|
8547
9522
|
var HOUR_MS2 = 60 * 6e4;
|
|
8548
9523
|
var DAY_MS = 24 * HOUR_MS2;
|
|
8549
9524
|
var STORE_MEASURE_INTERVAL_MS = 55 * 6e4;
|
|
9525
|
+
var ISOLATION_RETRY_FIRST_MS = 6e4;
|
|
9526
|
+
var ISOLATION_RETRY_MAX_MS = 10 * 6e4;
|
|
8550
9527
|
var DISK_PRESSURE_FREE_FRACTION = 0.2;
|
|
8551
9528
|
var STRANDED_PRUNE_BYTES = 20 * 1024 ** 3;
|
|
8552
9529
|
function flattenWanted(desired) {
|
|
@@ -8587,12 +9564,24 @@ var ClusterDaemon = class {
|
|
|
8587
9564
|
localImageHashes = /* @__PURE__ */ new Map();
|
|
8588
9565
|
/** How long the in-cluster registry has been not ready, across beats. */
|
|
8589
9566
|
registryWatch = { notReadySince: null };
|
|
9567
|
+
/**
|
|
9568
|
+
* Whether pod isolation is in force, from the last reconcile; undefined
|
|
9569
|
+
* while desired state does not ask for it. Every heartbeat checks it again
|
|
9570
|
+
* against k3s's own settings before reporting it.
|
|
9571
|
+
*/
|
|
9572
|
+
isolation = void 0;
|
|
9573
|
+
/** Whether the pod slice's CPU cap is in force, from the last reconcile. */
|
|
9574
|
+
cpuQuotaApplied = void 0;
|
|
8590
9575
|
timers = [];
|
|
8591
9576
|
/** When the last store walk started, so one that failed is not retried on every reconcile. */
|
|
8592
9577
|
storeWalkStartedAt = 0;
|
|
8593
9578
|
/** Serializes reconcile against itself — a sync nudge during a reconcile
|
|
8594
9579
|
* must queue, not interleave two kubectl applies of the same manifests. */
|
|
8595
9580
|
reconciling = null;
|
|
9581
|
+
/** The run queued behind the running one, until it starts. At most one waits. */
|
|
9582
|
+
queuedRun = null;
|
|
9583
|
+
/** The next reconcile while pod isolation is not applied; `dueAt` 0 = none. */
|
|
9584
|
+
isolationRetry = { delayMs: ISOLATION_RETRY_FIRST_MS, dueAt: 0 };
|
|
8596
9585
|
constructor(ports) {
|
|
8597
9586
|
this.ports = ports;
|
|
8598
9587
|
}
|
|
@@ -8628,10 +9617,27 @@ var ClusterDaemon = class {
|
|
|
8628
9617
|
}
|
|
8629
9618
|
/** Fetch the desired state and converge to it. Queued, never concurrent. */
|
|
8630
9619
|
syncAndReconcile() {
|
|
8631
|
-
|
|
9620
|
+
if (this.queuedRun) return this.queuedRun;
|
|
9621
|
+
const run = (this.reconciling ?? Promise.resolve()).then(() => {
|
|
9622
|
+
this.queuedRun = null;
|
|
9623
|
+
return this.runReconcile();
|
|
9624
|
+
});
|
|
9625
|
+
this.queuedRun = run;
|
|
8632
9626
|
this.reconciling = run.catch(() => void 0);
|
|
8633
9627
|
return run;
|
|
8634
9628
|
}
|
|
9629
|
+
/** Reconcile again later while pod isolation is not applied; stop once it is. */
|
|
9630
|
+
scheduleIsolationRetry() {
|
|
9631
|
+
if (this.isolation?.applied !== false) {
|
|
9632
|
+
this.isolationRetry = { delayMs: ISOLATION_RETRY_FIRST_MS, dueAt: 0 };
|
|
9633
|
+
return;
|
|
9634
|
+
}
|
|
9635
|
+
const { delayMs } = this.isolationRetry;
|
|
9636
|
+
this.isolationRetry = {
|
|
9637
|
+
delayMs: Math.min(delayMs * 2, ISOLATION_RETRY_MAX_MS),
|
|
9638
|
+
dueAt: Date.now() + delayMs
|
|
9639
|
+
};
|
|
9640
|
+
}
|
|
8635
9641
|
async runReconcile() {
|
|
8636
9642
|
try {
|
|
8637
9643
|
this.desired = await this.ports.connection.requestDesiredState();
|
|
@@ -8647,6 +9653,9 @@ var ClusterDaemon = class {
|
|
|
8647
9653
|
this.desired
|
|
8648
9654
|
);
|
|
8649
9655
|
this.warnings = result.warnings;
|
|
9656
|
+
this.isolation = result.isolation;
|
|
9657
|
+
this.cpuQuotaApplied = result.cpuQuotaApplied;
|
|
9658
|
+
this.scheduleIsolationRetry();
|
|
8650
9659
|
for (const warning of result.warnings) this.ports.log(warning);
|
|
8651
9660
|
if (result.prunedNamespaces.length > 0) {
|
|
8652
9661
|
this.ports.log(
|
|
@@ -8802,6 +9811,11 @@ var ClusterDaemon = class {
|
|
|
8802
9811
|
);
|
|
8803
9812
|
}
|
|
8804
9813
|
for (const failure of result.failures) this.ports.log(`Image prune failed \u2014 ${failure}`);
|
|
9814
|
+
if (result.pending.length > 0) {
|
|
9815
|
+
this.ports.log(
|
|
9816
|
+
`Image removal still in progress for ${result.pending.length} image(s); the next prune re-checks them.`
|
|
9817
|
+
);
|
|
9818
|
+
}
|
|
8805
9819
|
} catch (error) {
|
|
8806
9820
|
this.ports.log(
|
|
8807
9821
|
`Skipping image prune: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -8817,6 +9831,10 @@ var ClusterDaemon = class {
|
|
|
8817
9831
|
const inventory = await readImageInventory(this.ports.exec).catch(() => null);
|
|
8818
9832
|
const registryOn = this.desired?.localRegistry?.enabled === true;
|
|
8819
9833
|
if (!registryOn) this.registryWatch.notReadySince = null;
|
|
9834
|
+
const isolation = await recheckIsolation(
|
|
9835
|
+
{ exec: this.ports.exec, fs: this.ports.fs },
|
|
9836
|
+
this.isolation
|
|
9837
|
+
);
|
|
8820
9838
|
return await collectHeartbeat(
|
|
8821
9839
|
{
|
|
8822
9840
|
exec: this.ports.exec,
|
|
@@ -8831,10 +9849,12 @@ var ClusterDaemon = class {
|
|
|
8831
9849
|
lastPruneAt: this.state?.lastPruneAt ?? null,
|
|
8832
9850
|
lastWarmAt: this.state?.lastWarmAt ?? null,
|
|
8833
9851
|
lastUpdateAt: this.state?.lastUpdateAt ?? null,
|
|
8834
|
-
localWarnings: [...this.warnings, ...this.warmWarnings],
|
|
9852
|
+
localWarnings: [...this.warnings, ...this.warmWarnings, ...isolation.warnings],
|
|
8835
9853
|
imageGcHighThreshold: this.effectiveImageGcHigh,
|
|
8836
9854
|
localImageHashes: this.localImageHashes,
|
|
8837
9855
|
...registryOn ? { localRegistry: this.registryWatch } : {},
|
|
9856
|
+
...isolation.isolation ? { isolation: isolation.isolation } : {},
|
|
9857
|
+
cpuQuotaApplied: this.cpuQuotaApplied,
|
|
8838
9858
|
k3sStoreBytes: this.measuredStoreBytes
|
|
8839
9859
|
},
|
|
8840
9860
|
inventory
|
|
@@ -8859,6 +9879,10 @@ var ClusterDaemon = class {
|
|
|
8859
9879
|
return this.beating;
|
|
8860
9880
|
}
|
|
8861
9881
|
async runBeat() {
|
|
9882
|
+
if (this.isolationRetry.dueAt > 0 && Date.now() >= this.isolationRetry.dueAt) {
|
|
9883
|
+
this.isolationRetry.dueAt = 0;
|
|
9884
|
+
void this.syncAndReconcile();
|
|
9885
|
+
}
|
|
8862
9886
|
try {
|
|
8863
9887
|
const workloads = await this.ports.connection.sendHeartbeat({
|
|
8864
9888
|
...await this.buildHeartbeat(),
|
|
@@ -8965,58 +9989,10 @@ function renderAllUnits(options) {
|
|
|
8965
9989
|
};
|
|
8966
9990
|
}
|
|
8967
9991
|
|
|
8968
|
-
// src/lib/fs-port.ts
|
|
8969
|
-
import { chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
8970
|
-
var nodeFileSystem = {
|
|
8971
|
-
async readFile(path) {
|
|
8972
|
-
try {
|
|
8973
|
-
return await readFile(path, "utf8");
|
|
8974
|
-
} catch {
|
|
8975
|
-
return null;
|
|
8976
|
-
}
|
|
8977
|
-
},
|
|
8978
|
-
async writeFile(path, content, mode) {
|
|
8979
|
-
await mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true });
|
|
8980
|
-
await writeFile(path, content, "utf8");
|
|
8981
|
-
if (mode !== void 0) await chmod(path, mode);
|
|
8982
|
-
},
|
|
8983
|
-
async removeFile(path) {
|
|
8984
|
-
await rm(path, { force: true });
|
|
8985
|
-
},
|
|
8986
|
-
async ensureDir(path, mode) {
|
|
8987
|
-
await mkdir(path, { recursive: true });
|
|
8988
|
-
if (mode !== void 0) await chmod(path, mode);
|
|
8989
|
-
}
|
|
8990
|
-
};
|
|
8991
|
-
function createMemoryFileSystem(seed = {}) {
|
|
8992
|
-
const files = new Map(Object.entries(seed));
|
|
8993
|
-
const modes = /* @__PURE__ */ new Map();
|
|
8994
|
-
const removed = [];
|
|
8995
|
-
return {
|
|
8996
|
-
files,
|
|
8997
|
-
modes,
|
|
8998
|
-
removed,
|
|
8999
|
-
readFile: (path) => Promise.resolve(files.get(path) ?? null),
|
|
9000
|
-
writeFile: (path, content, mode) => {
|
|
9001
|
-
files.set(path, content);
|
|
9002
|
-
if (mode !== void 0) modes.set(path, mode);
|
|
9003
|
-
return Promise.resolve();
|
|
9004
|
-
},
|
|
9005
|
-
removeFile: (path) => {
|
|
9006
|
-
files.delete(path);
|
|
9007
|
-
removed.push(path);
|
|
9008
|
-
return Promise.resolve();
|
|
9009
|
-
},
|
|
9010
|
-
ensureDir: (path, mode) => {
|
|
9011
|
-
if (mode !== void 0) modes.set(path, mode);
|
|
9012
|
-
return Promise.resolve();
|
|
9013
|
-
}
|
|
9014
|
-
};
|
|
9015
|
-
}
|
|
9016
|
-
|
|
9017
9992
|
// src/verbs.ts
|
|
9018
9993
|
var VERBS = [
|
|
9019
9994
|
"vm",
|
|
9995
|
+
"host",
|
|
9020
9996
|
"setup",
|
|
9021
9997
|
"start",
|
|
9022
9998
|
"stop",
|
|
@@ -9049,14 +10025,22 @@ function parseArgs(argv) {
|
|
|
9049
10025
|
}
|
|
9050
10026
|
return { verb, positional, flags };
|
|
9051
10027
|
}
|
|
9052
|
-
var HELP = `conveyor-k3 \u2014
|
|
10028
|
+
var HELP = `conveyor-k3 \u2014 Personal Compute for Conveyor
|
|
9053
10029
|
|
|
9054
10030
|
Usage: conveyor-k3 <command> [options]
|
|
9055
10031
|
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
10032
|
+
One product, two runtimes. Pick the one that fits the machine:
|
|
10033
|
+
|
|
10034
|
+
On your computer or server:
|
|
10035
|
+
vm Personal computer runtime: an isolated VM on macOS or
|
|
10036
|
+
Linux. Run conveyor-k3 vm for its commands.
|
|
10037
|
+
host Dedicated server runtime: k3s and the daemon on this
|
|
10038
|
+
Linux machine, with no VM. Root only. Run
|
|
10039
|
+
conveyor-k3 host for its commands.
|
|
10040
|
+
|
|
10041
|
+
Inside a linked machine (the VM guest, or the server itself):
|
|
10042
|
+
setup VM guest only: install and enrol this machine. Safe
|
|
10043
|
+
to re-run. On a server, use conveyor-k3 host setup.
|
|
9060
10044
|
start Start the tunnel and the daemon.
|
|
9061
10045
|
stop Stop both, and tell Conveyor at once so your next
|
|
9062
10046
|
build goes straight to project compute.
|
|
@@ -9081,10 +10065,12 @@ Commands:
|
|
|
9081
10065
|
into this node and pin it. Writes one file under
|
|
9082
10066
|
/etc/sudoers.d, checked with visudo first. Needed only
|
|
9083
10067
|
when a project has Bake Locally turned on.
|
|
9084
|
-
uninstall
|
|
9085
|
-
registry. Your k3s config, your own
|
|
9086
|
-
entries, your workloads, and the
|
|
9087
|
-
left alone.
|
|
10068
|
+
uninstall VM guest only: remove Conveyor's units, environment,
|
|
10069
|
+
state, and image registry. Your k3s config, your own
|
|
10070
|
+
registries.yaml entries, your workloads, and the
|
|
10071
|
+
registry's data are left alone. On a server, use
|
|
10072
|
+
conveyor-k3 host uninstall: it also removes the
|
|
10073
|
+
device key.
|
|
9088
10074
|
--purge-k3s --yes also remove k3s itself and the
|
|
9089
10075
|
registry's data
|
|
9090
10076
|
daemon Run in the foreground (what the systemd unit calls).
|
|
@@ -9168,19 +10154,79 @@ async function removeLocalRegistry(ports) {
|
|
|
9168
10154
|
`Could not remove the local image registry from the cluster: ${removed.stderr.trim().slice(0, 200)}`
|
|
9169
10155
|
);
|
|
9170
10156
|
}
|
|
10157
|
+
for (const [namespace, name] of [
|
|
10158
|
+
[PERSONAL_LOCAL_REGISTRY_NAMESPACE, LOCAL_REGISTRY_HTPASSWD_SECRET],
|
|
10159
|
+
[PERSONAL_BAKE_NAMESPACE, PERSONAL_LOCAL_REGISTRY_PUSH_SECRET]
|
|
10160
|
+
]) {
|
|
10161
|
+
const secret = await ports.exec(
|
|
10162
|
+
"k3s",
|
|
10163
|
+
["kubectl", "-n", namespace, "delete", "secret", name, "--ignore-not-found"],
|
|
10164
|
+
{ timeoutMs: 6e4 }
|
|
10165
|
+
);
|
|
10166
|
+
if (secret.exitCode !== 0) {
|
|
10167
|
+
ports.log(
|
|
10168
|
+
`Could not remove the local image registry's Secret ${namespace}/${name}: ${secret.stderr.trim().slice(0, 200)}`
|
|
10169
|
+
);
|
|
10170
|
+
}
|
|
10171
|
+
}
|
|
9171
10172
|
const raw = await ports.fs.readFile(REGISTRIES_FILE);
|
|
9172
10173
|
const parsed = parseRegistries(raw);
|
|
9173
|
-
|
|
10174
|
+
const next = LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
10175
|
+
removeRegistryAuth,
|
|
10176
|
+
removeRegistryMirror(parsed, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
10177
|
+
);
|
|
10178
|
+
if (serializeRegistries(next) === serializeRegistries(parsed)) return;
|
|
9174
10179
|
const backup = await writeRegistriesWithBackup(
|
|
9175
10180
|
ports.fs,
|
|
9176
10181
|
raw,
|
|
9177
|
-
|
|
10182
|
+
next,
|
|
9178
10183
|
(ports.now ?? (() => /* @__PURE__ */ new Date()))()
|
|
9179
10184
|
);
|
|
9180
10185
|
ports.log(
|
|
9181
|
-
`Removed the local image registry's mirror from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
10186
|
+
`Removed the local image registry's mirror and credential from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
9182
10187
|
);
|
|
9183
10188
|
}
|
|
10189
|
+
async function removeIsolation(ports) {
|
|
10190
|
+
const [key, value] = Object.entries(MANAGED_BY2)[0] ?? [];
|
|
10191
|
+
const removed = await ports.exec(
|
|
10192
|
+
"k3s",
|
|
10193
|
+
["kubectl", "delete", "networkpolicy", "-A", "-l", `${key}=${value}`, "--ignore-not-found"],
|
|
10194
|
+
{ timeoutMs: 12e4 }
|
|
10195
|
+
);
|
|
10196
|
+
if (removed.exitCode !== 0) {
|
|
10197
|
+
ports.log(
|
|
10198
|
+
`Could not remove Conveyor's network policies: ${removed.stderr.trim().slice(0, 200)}`
|
|
10199
|
+
);
|
|
10200
|
+
}
|
|
10201
|
+
const listed = await ports.exec("k3s", [
|
|
10202
|
+
"kubectl",
|
|
10203
|
+
"get",
|
|
10204
|
+
"namespaces",
|
|
10205
|
+
"-o",
|
|
10206
|
+
"jsonpath={.items[*].metadata.name}"
|
|
10207
|
+
]);
|
|
10208
|
+
if (listed.exitCode !== 0) {
|
|
10209
|
+
ports.log(
|
|
10210
|
+
`Could not list namespaces to remove Conveyor's Pod Security labels: ${listed.stderr.trim().slice(0, 200)}`
|
|
10211
|
+
);
|
|
10212
|
+
return;
|
|
10213
|
+
}
|
|
10214
|
+
const namespaces = listed.stdout.split(/\s+/).filter((name) => name.startsWith("claudespace-") || name === PERSONAL_BAKE_NAMESPACE);
|
|
10215
|
+
for (const namespace of namespaces) {
|
|
10216
|
+
const unlabelled = await ports.exec("k3s", [
|
|
10217
|
+
"kubectl",
|
|
10218
|
+
"label",
|
|
10219
|
+
"namespace",
|
|
10220
|
+
namespace,
|
|
10221
|
+
...PSA_LABEL_KEYS.map((labelKey) => `${labelKey}-`)
|
|
10222
|
+
]);
|
|
10223
|
+
if (unlabelled.exitCode !== 0) {
|
|
10224
|
+
ports.log(
|
|
10225
|
+
`Could not remove Conveyor's Pod Security labels from ${namespace}: ${unlabelled.stderr.trim().slice(0, 200)}`
|
|
10226
|
+
);
|
|
10227
|
+
}
|
|
10228
|
+
}
|
|
10229
|
+
}
|
|
9184
10230
|
async function uninstall(ports, options = {}) {
|
|
9185
10231
|
const result = { stoppedUnits: [], removedFiles: [], purgedK3s: false };
|
|
9186
10232
|
if (options.purgeK3s && !options.confirmed) {
|
|
@@ -9193,6 +10239,11 @@ async function uninstall(ports, options = {}) {
|
|
|
9193
10239
|
result.stoppedUnits.push(unit);
|
|
9194
10240
|
}
|
|
9195
10241
|
await removeLocalRegistry(ports);
|
|
10242
|
+
await removeIsolation(ports);
|
|
10243
|
+
const lifted = await liftPodCpuQuota(ports.exec).catch(
|
|
10244
|
+
(error) => error instanceof Error ? error.message : String(error)
|
|
10245
|
+
);
|
|
10246
|
+
if (lifted) ports.log(`Could not lift Conveyor's CPU cap on kubepods.slice: ${lifted}`);
|
|
9196
10247
|
for (const path of OWNED_FILES) {
|
|
9197
10248
|
await ports.fs.removeFile(path);
|
|
9198
10249
|
result.removedFiles.push(path);
|
|
@@ -9245,6 +10296,45 @@ async function uninstall(ports, options = {}) {
|
|
|
9245
10296
|
return result;
|
|
9246
10297
|
}
|
|
9247
10298
|
|
|
10299
|
+
// src/lib/bins.ts
|
|
10300
|
+
var INSTALLER_PATHS = {
|
|
10301
|
+
bun: "/root/.bun/bin/bun",
|
|
10302
|
+
cli: "/root/.bun/bin/conveyor-k3",
|
|
10303
|
+
cloudflared: "/usr/local/bin/cloudflared"
|
|
10304
|
+
};
|
|
10305
|
+
var CLI_PATH_LINK = "/usr/local/bin/conveyor-k3";
|
|
10306
|
+
async function linkCliOnPath(exec, target = INSTALLER_PATHS.cli) {
|
|
10307
|
+
if (target === CLI_PATH_LINK) return { linked: false };
|
|
10308
|
+
const present = await exec("test", ["-x", target]).catch(() => null);
|
|
10309
|
+
if (present?.exitCode !== 0) return { linked: false };
|
|
10310
|
+
const link = await exec("ln", ["-sfn", target, CLI_PATH_LINK]).catch(() => null);
|
|
10311
|
+
if (link?.exitCode === 0) return { linked: true };
|
|
10312
|
+
return {
|
|
10313
|
+
linked: false,
|
|
10314
|
+
problem: `Could not link ${CLI_PATH_LINK} to ${target}: ${link?.stderr.trim().slice(0, 200) || "ln failed"}`
|
|
10315
|
+
};
|
|
10316
|
+
}
|
|
10317
|
+
|
|
10318
|
+
// src/lib/cloudflared.ts
|
|
10319
|
+
var PACKAGE_MANAGER_REFUSAL = "installed by a package manager";
|
|
10320
|
+
async function resolveRealPath(exec, bin) {
|
|
10321
|
+
const result = await exec("readlink", ["-f", bin]).catch(() => null);
|
|
10322
|
+
const resolved = result?.exitCode === 0 ? result.stdout.trim() : "";
|
|
10323
|
+
return resolved.startsWith("/") ? resolved : bin;
|
|
10324
|
+
}
|
|
10325
|
+
async function cloudflaredUpdater(exec, bin) {
|
|
10326
|
+
const real = await resolveRealPath(exec, bin);
|
|
10327
|
+
const owner = await exec("dpkg", ["-S", real]).catch(() => null);
|
|
10328
|
+
return owner?.exitCode === 0 ? "package-manager" : "conveyor";
|
|
10329
|
+
}
|
|
10330
|
+
function isPackageManagerRefusal(output) {
|
|
10331
|
+
return `${output.stdout}
|
|
10332
|
+
${output.stderr}`.toLowerCase().includes(PACKAGE_MANAGER_REFUSAL);
|
|
10333
|
+
}
|
|
10334
|
+
function describeCloudflaredUpdater(updater) {
|
|
10335
|
+
return updater === "package-manager" ? "cloudflared is updated by your package manager, not by Conveyor" : "cloudflared is updated by Conveyor's daily update";
|
|
10336
|
+
}
|
|
10337
|
+
|
|
9248
10338
|
// src/commands/update.ts
|
|
9249
10339
|
var CLOUDFLARED_UPDATED_EXIT = 11;
|
|
9250
10340
|
function readExecStart(unit) {
|
|
@@ -9259,12 +10349,6 @@ function readExecStart(unit) {
|
|
|
9259
10349
|
}
|
|
9260
10350
|
return collected.join(" ");
|
|
9261
10351
|
}
|
|
9262
|
-
function bareToken(token) {
|
|
9263
|
-
let bare = token.trim();
|
|
9264
|
-
if (bare.endsWith("\\")) bare = bare.slice(0, -1).trim();
|
|
9265
|
-
if (bare.length >= 2 && bare.startsWith("'") && bare.endsWith("'")) bare = bare.slice(1, -1);
|
|
9266
|
-
return bare;
|
|
9267
|
-
}
|
|
9268
10352
|
function parseForeignExecFlags(execStart) {
|
|
9269
10353
|
const ours = new Set(K3S_INSTALL_EXEC.split(/\s+/).filter((token) => token.startsWith("--")));
|
|
9270
10354
|
const foreign = [];
|
|
@@ -9284,6 +10368,12 @@ async function foreignK3sExecFlags(exec) {
|
|
|
9284
10368
|
return parseForeignExecFlags(readExecStart(unit.stdout));
|
|
9285
10369
|
}
|
|
9286
10370
|
function decideK3sUpgrade(input) {
|
|
10371
|
+
if (input.managesK3s === false) {
|
|
10372
|
+
return {
|
|
10373
|
+
upgrade: false,
|
|
10374
|
+
reason: "Conveyor did not install this server's k3s, so its owner upgrades it"
|
|
10375
|
+
};
|
|
10376
|
+
}
|
|
9287
10377
|
if (!input.autoUpgradeK3s) {
|
|
9288
10378
|
return { upgrade: false, reason: "automatic k3s upgrades are turned off for this machine" };
|
|
9289
10379
|
}
|
|
@@ -9332,7 +10422,22 @@ async function updatePackage(ports, result, targetVersion) {
|
|
|
9332
10422
|
await ports.exec("systemctl", ["restart", DAEMON_UNIT]);
|
|
9333
10423
|
}
|
|
9334
10424
|
async function updateCloudflared(ports, result) {
|
|
10425
|
+
const packaged = () => {
|
|
10426
|
+
result.cloudflaredUpdater = "package-manager";
|
|
10427
|
+
result.notes.push(
|
|
10428
|
+
`cloudflared update skipped: ${describeCloudflaredUpdater("package-manager")}`
|
|
10429
|
+
);
|
|
10430
|
+
};
|
|
10431
|
+
if (await cloudflaredUpdater(ports.exec, ports.bins.cloudflared) === "package-manager") {
|
|
10432
|
+
packaged();
|
|
10433
|
+
return;
|
|
10434
|
+
}
|
|
9335
10435
|
const update = await ports.exec(ports.bins.cloudflared, ["update"], { timeoutMs: 5 * 6e4 });
|
|
10436
|
+
if (isPackageManagerRefusal(update)) {
|
|
10437
|
+
packaged();
|
|
10438
|
+
return;
|
|
10439
|
+
}
|
|
10440
|
+
result.cloudflaredUpdater = "conveyor";
|
|
9336
10441
|
if (update.exitCode !== CLOUDFLARED_UPDATED_EXIT) return;
|
|
9337
10442
|
result.cloudflaredUpdated = true;
|
|
9338
10443
|
ports.log("cloudflared was updated; restarting the tunnel.");
|
|
@@ -9342,10 +10447,13 @@ async function runUpdate(ports, decision, channel, targetVersion) {
|
|
|
9342
10447
|
const result = {
|
|
9343
10448
|
packageUpdated: false,
|
|
9344
10449
|
cloudflaredUpdated: false,
|
|
10450
|
+
cloudflaredUpdater: null,
|
|
9345
10451
|
k3sUpgraded: false,
|
|
9346
10452
|
notes: []
|
|
9347
10453
|
};
|
|
9348
10454
|
await updatePackage(ports, result, targetVersion);
|
|
10455
|
+
const linked = await linkCliOnPath(ports.exec);
|
|
10456
|
+
if (linked.problem) result.notes.push(linked.problem);
|
|
9349
10457
|
await updateCloudflared(ports, result);
|
|
9350
10458
|
if (!decision.upgrade) {
|
|
9351
10459
|
result.notes.push(`k3s upgrade skipped: ${decision.reason}`);
|
|
@@ -9378,6 +10486,9 @@ async function runUpdate(ports, decision, channel, targetVersion) {
|
|
|
9378
10486
|
return result;
|
|
9379
10487
|
}
|
|
9380
10488
|
|
|
10489
|
+
// src/lib/env.ts
|
|
10490
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
10491
|
+
|
|
9381
10492
|
// src/commands/setup.ts
|
|
9382
10493
|
var K3S_MIN_MAJOR_MINOR = [1, 29];
|
|
9383
10494
|
function parseK3sVersion(output) {
|
|
@@ -10381,7 +11492,6 @@ export {
|
|
|
10381
11492
|
renderAllUnits,
|
|
10382
11493
|
renderGuestDnsProvision,
|
|
10383
11494
|
renderK3sDropIn,
|
|
10384
|
-
renderKubeletDropIn,
|
|
10385
11495
|
renderRegistryAuthSummary,
|
|
10386
11496
|
resolveCloudflaredSrvRecords,
|
|
10387
11497
|
resolveNetwork,
|