@rallycry/conveyor-k3 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +968 -256
- package/dist/index.d.ts +37 -30
- package/dist/index.js +839 -216
- package/dist/vm-qemu.js +6472 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ var __export = (target2, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/lib/paths.ts
|
|
13
|
-
var ETC_CONVEYOR, ENV_FILE, TUNNEL_ENV_FILE, STATE_DIR, STATE_FILE, LOCAL_REGISTRY_DATA_DIR, LOCAL_REGISTRY_MIRROR_RESTART_FILE, RANCHER_DIR, K3S_OWNER_CONFIG, K3S_CONFIG_DROPIN_DIR, K3S_CONFIG_DROPIN, REGISTRIES_FILE, K3S_CERTS_D, K3S_SERVER_CA, K3S_CONTAINERD_DIR, JOURNALD_DROPIN, CLOUDFLARED_DIR, CA_POOL_PATH, SYSTEMD_DIR, DAEMON_UNIT, TUNNEL_UNIT, UPDATE_UNIT, UPDATE_TIMER, ALL_UNITS, OWNED_FILES, RUNNERS_SUDOERS_FILE;
|
|
13
|
+
var ETC_CONVEYOR, ENV_FILE, TUNNEL_ENV_FILE, STATE_DIR, STATE_FILE, LOCAL_REGISTRY_DATA_DIR, LOCAL_REGISTRY_MIRROR_RESTART_FILE, LOCAL_REGISTRY_CREDENTIALS_FILE, LOCAL_REGISTRY_AUTH_STATE_FILE, RANCHER_DIR, K3S_OWNER_CONFIG, K3S_CONFIG_DROPIN_DIR, K3S_CONFIG_DROPIN, REGISTRIES_FILE, K3S_CERTS_D, K3S_SERVER_CA, K3S_CONTAINERD_DIR, JOURNALD_DROPIN, CLOUDFLARED_DIR, CA_POOL_PATH, SYSTEMD_DIR, DAEMON_UNIT, TUNNEL_UNIT, UPDATE_UNIT, UPDATE_TIMER, ALL_UNITS, OWNED_FILES, RUNNERS_SUDOERS_FILE;
|
|
14
14
|
var init_paths = __esm({
|
|
15
15
|
"src/lib/paths.ts"() {
|
|
16
16
|
"use strict";
|
|
@@ -21,6 +21,8 @@ var init_paths = __esm({
|
|
|
21
21
|
STATE_FILE = `${STATE_DIR}/state.json`;
|
|
22
22
|
LOCAL_REGISTRY_DATA_DIR = `${STATE_DIR}/registry`;
|
|
23
23
|
LOCAL_REGISTRY_MIRROR_RESTART_FILE = `${STATE_DIR}/registry-mirror-restart`;
|
|
24
|
+
LOCAL_REGISTRY_CREDENTIALS_FILE = `${STATE_DIR}/registry-credentials.json`;
|
|
25
|
+
LOCAL_REGISTRY_AUTH_STATE_FILE = `${STATE_DIR}/registry-auth.json`;
|
|
24
26
|
RANCHER_DIR = "/etc/rancher/k3s";
|
|
25
27
|
K3S_OWNER_CONFIG = `${RANCHER_DIR}/config.yaml`;
|
|
26
28
|
K3S_CONFIG_DROPIN_DIR = `${RANCHER_DIR}/config.yaml.d`;
|
|
@@ -216,17 +218,44 @@ var init_safe_arg = __esm({
|
|
|
216
218
|
});
|
|
217
219
|
|
|
218
220
|
// src/lib/env.ts
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
const
|
|
221
|
+
import { readFileSync } from "fs";
|
|
222
|
+
function parseEnvFile(content) {
|
|
223
|
+
const values = {};
|
|
224
|
+
for (const raw of content.split("\n")) {
|
|
225
|
+
const line = raw.trim();
|
|
226
|
+
if (!line || line.startsWith("#")) continue;
|
|
227
|
+
const eq = line.indexOf("=");
|
|
228
|
+
if (eq <= 0) continue;
|
|
229
|
+
const key = line.slice(0, eq).trim();
|
|
230
|
+
let value = line.slice(eq + 1).trim();
|
|
231
|
+
if (value.length >= 2 && (value[0] === '"' || value[0] === "'") && value.endsWith(value[0])) {
|
|
232
|
+
value = value.slice(1, -1);
|
|
233
|
+
}
|
|
234
|
+
values[key] = value;
|
|
235
|
+
}
|
|
236
|
+
return values;
|
|
237
|
+
}
|
|
238
|
+
function readCliEnv(env = process.env, readEnvFile = readEnvFileFromDisk) {
|
|
239
|
+
let source = env;
|
|
240
|
+
if (!env.CONVEYOR_API_URL || !env.CONVEYOR_CLUSTER_TOKEN) {
|
|
241
|
+
const content = readEnvFile?.() ?? null;
|
|
242
|
+
if (content !== null) source = { ...parseEnvFile(content), ...definedOnly(env) };
|
|
243
|
+
}
|
|
244
|
+
const apiUrl = source.CONVEYOR_API_URL ?? "";
|
|
245
|
+
const clusterToken = source.CONVEYOR_CLUSTER_TOKEN ?? "";
|
|
246
|
+
const clusterId = source.CONVEYOR_CLUSTER_ID ?? "";
|
|
223
247
|
if (!apiUrl || !clusterToken) {
|
|
224
248
|
throw new Error(
|
|
225
|
-
|
|
249
|
+
`This machine is not enrolled. ${ENV_FILE} must set CONVEYOR_API_URL and CONVEYOR_CLUSTER_TOKEN \u2014 re-run the install command from your Conveyor profile.`
|
|
226
250
|
);
|
|
227
251
|
}
|
|
228
252
|
return { apiUrl, clusterToken, clusterId };
|
|
229
253
|
}
|
|
254
|
+
function definedOnly(env) {
|
|
255
|
+
return Object.fromEntries(
|
|
256
|
+
Object.entries(env).filter((entry) => Boolean(entry[1]))
|
|
257
|
+
);
|
|
258
|
+
}
|
|
230
259
|
async function preflight(exec) {
|
|
231
260
|
const problems = [];
|
|
232
261
|
if (process.platform !== "linux") {
|
|
@@ -250,11 +279,20 @@ async function preflight(exec) {
|
|
|
250
279
|
function checkedHostname(hostname2) {
|
|
251
280
|
return assertHostname(hostname2, "tunnel hostname");
|
|
252
281
|
}
|
|
282
|
+
var readEnvFileFromDisk;
|
|
253
283
|
var init_env = __esm({
|
|
254
284
|
"src/lib/env.ts"() {
|
|
255
285
|
"use strict";
|
|
256
286
|
init_safe_arg();
|
|
257
287
|
init_exec();
|
|
288
|
+
init_paths();
|
|
289
|
+
readEnvFileFromDisk = () => {
|
|
290
|
+
try {
|
|
291
|
+
return readFileSync(ENV_FILE, "utf8");
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
258
296
|
}
|
|
259
297
|
});
|
|
260
298
|
|
|
@@ -4374,7 +4412,7 @@ var init_zod = __esm({
|
|
|
4374
4412
|
}
|
|
4375
4413
|
});
|
|
4376
4414
|
|
|
4377
|
-
// ../shared/dist/chunk-
|
|
4415
|
+
// ../shared/dist/chunk-SFSQ7NKP.js
|
|
4378
4416
|
function formatModelId(provider, model) {
|
|
4379
4417
|
return `${provider}/${model}`;
|
|
4380
4418
|
}
|
|
@@ -4392,8 +4430,8 @@ function uniqueSortedArray(item, minimum = 0) {
|
|
|
4392
4430
|
}).transform((values) => [...values].sort());
|
|
4393
4431
|
}
|
|
4394
4432
|
var DEFAULT_SONNET_MODEL, DEFAULT_OPUS_MODEL, DEFAULT_HAIKU_MODEL, FABLE_MODEL, PREVIOUS_SONNET_MODEL, PREVIOUS_OPUS_MODEL, JEV_MODEL, JEV_MODEL_ID, JEV_ZEN_MODEL, JEV_ZEN_MODEL_ID, JEV_INPUT_PRICE_PER_TOKEN, JEV_STATE_MAX_CHARS, PTY_STREAM_PORT_BASE, PTY_STREAM_PORT_ATTEMPTS, PREVIEW_PORT_DENY_LIST, checkpointPathSchema, secretNameSchema, checkpointKeySchema, checkpointDigestRefSchema, ACTIONS_PREBAKE_REGISTRY_PATTERN, actionsPrebakeRegistrySchema, projectCheckpointSettingsSchema, CARD_DESCRIPTION_MAX, CARD_DESCRIPTION_LIMIT_MESSAGE, CARD_DESCRIPTION_FIELD_HINT, MAX_MANUAL_TESTS_PER_TASK, MAX_MANUAL_TEST_TITLE_LENGTH, ManualTestItemsSchema, MAX_CI_WAIT_TIMEOUT_MINUTES;
|
|
4395
|
-
var
|
|
4396
|
-
"../shared/dist/chunk-
|
|
4433
|
+
var init_chunk_SFSQ7NKP = __esm({
|
|
4434
|
+
"../shared/dist/chunk-SFSQ7NKP.js"() {
|
|
4397
4435
|
"use strict";
|
|
4398
4436
|
init_zod();
|
|
4399
4437
|
init_zod();
|
|
@@ -4519,11 +4557,56 @@ function localBakeSudoersCommands(k3sPath) {
|
|
|
4519
4557
|
return `${k3sPath} ctr ${command.args.join(" ")}${suffix}`;
|
|
4520
4558
|
});
|
|
4521
4559
|
}
|
|
4522
|
-
var ACHIEVEMENT_RARITIES, RISK_LEVELS, riskLevelSchema, DEFAULT_RISK_LEVELS, LEVEL_BY_VALUE, ACTIVE_WORK_STATUSES, IDENTIFIED_WORK_STATUSES, DEFAULT_TASK_STATUS_COLOR, DEFAULT_TASK_STATUS_COLOR_INT, MAX_FILE_SIZE_BYTES, MAX_FILE_TAGS, MAX_FILE_TAG_LENGTH, ALLOWED_FILE_MIME_TYPES, ALLOWED_FILE_ACCEPT, EMBED_THRESHOLD_IMAGES, EMBED_THRESHOLD_TEXT, IDLE_HEARTBEAT_MS, TurnEndToolCallSchema, AgentTurnUsageSchema, KnownAgentEventSchema, AgentEventSchema, PtyChatAtSchema, PtyChatTurnUsageSchema, PtyChatTurnFileSchema, PTY_CHAT_TURN_FILES_MAX, PtyChatEventPayloadSchema, PtyChatEventRequestSchema, PtyChatAttachRequestSchema, PTY_EFFORT_LEVELS, SetPtyModelEffortRequestSchema, cardDescription, AgentHeartbeatSchema, CreatePRInputSchema, PostToChatInputSchema, GetTaskContextRequestSchema, GetChatMessagesRequestSchema, GetTaskFilesRequestSchema, GetTaskFileRequestSchema, DeleteTaskFileRequestSchema, SetTaskFileShowcaseRequestSchema, GetTaskRequestSchema, GetCliHistoryRequestSchema, ListSubtasksRequestSchema, GetDependenciesRequestSchema, GetSuggestionsRequestSchema, ListManualTestsRequestSchema, QueryManualTestsRequestSchema, CreatePullRequestRequestSchema, RequestFileUploadRequestSchema, ConfirmFileUploadRequestSchema, UpdateTaskStatusRequestSchema, StoreSessionIdRequestSchema, SetManualTestsRequestSchema, EditManualTestRequestSchema, RemoveManualTestRequestSchema, ApproveManualTestRequestSchema, RejectManualTestRequestSchema, SessionStartRequestSchema, SessionStopRequestSchema, EndReviewSessionRequestSchema, ConnectAgentRequestSchema, ReportAgentStatusRequestSchema, NotifyAgentVersionRequestSchema, DiscoveredPortSchema, ReportDiscoveredPortsRequestSchema, ReportBootMilestoneRequestSchema, CreateSubtaskRequestSchema, UpdateSubtaskRequestSchema, DeleteSubtaskRequestSchema, SetSubtaskParentRequestSchema, GetTaskPropertiesRequestSchema, UpdateTaskFieldsRequestSchema, UpdateTaskPropertiesRequestSchema, ListIconsRequestSchema, GenerateTaskIconRequestSchema, SearchFaIconsRequestSchema, PickFaIconRequestSchema, CreateFollowUpTaskRequestSchema, AddDependencyRequestSchema, RemoveDependencyRequestSchema, CreateSuggestionRequestSchema, VoteSuggestionRequestSchema, TriggerIdentificationRequestSchema, HandoffToImplementerRequestSchema, SubmitCodeReviewResultRequestSchema, CycleCodingAgentKeyRequestSchema, PostChildChatMessageRequestSchema, UpdateChildStatusRequestSchema, GetAgentStatusRequestSchema, GetUiCliHistoryRequestSchema, GetActivePtySessionRequestSchema, ListActivePtySessionsRequestSchema, SendSoftStopRequestSchema, StopTaskSessionRequestSchema, FlushTaskQueueRequestSchema, CancelTaskQueuedMessageRequestSchema, FlushSingleQueuedMessageRequestSchema, AnswerAgentQuestionRequestSchema, ClearAgentTodosRequestSchema, AgentQuestionOptionSchema, AgentQuestionSchema, AskUserQuestionRequestSchema, PostAgentMessageRequestSchema, EmitAgentEventRequestSchema, RefreshGithubTokenRequestSchema, ReportCredentialFailureRequestSchema, ReportReviewSpawnFailureRequestSchema, ReportBuilderSpawnFailureRequestSchema, SpawnTaskSessionRequestSchema, StartCodeReviewRequestSchema, StopCodeReviewRequestSchema, ReportSessionSpawnFailureRequestSchema, RefreshGithubTokenResponseSchema, PTY_FRAME_MAX_CHARS, PTY_MAX_DIMENSION, PtyOutputRequestSchema, PtyEndedRequestSchema, PtyInputRequestSchema, PtyResizeRequestSchema, PtyAttachRequestSchema, ReportPtyStreamRequestSchema, ReportPtyInputRequestSchema, GetPtyStreamEndpointRequestSchema, CreatePRResponseSchema, PostToChatResponseSchema, UpdateTaskStatusResponseSchema, StoreSessionIdResponseSchema, HeartbeatResponseSchema, SessionStartResponseSchema, SessionStopResponseSchema, DeleteSubtaskResponseSchema, ParkOnCheckResultRequestSchema, GIT_BRANCH_NAME_MAX, GIT_BRANCH_NAME_MESSAGE, ALLOWED_REF, cardDescription2, ListAccessibleProjectsRequestSchema, ListProjectTasksRequestSchema, GetProjectTaskRequestSchema, SearchProjectTasksRequestSchema, ListProjectTagsRequestSchema, GetProjectTagRequestSchema, ListProjectTagAttachmentsRequestSchema, SetProjectFileTagsRequestSchema, GetProjectSummaryRequestSchema, GetProjectOnboardingStatusRequestSchema, GetProjectOnboardingStepRequestSchema, GetProjectConnectUrlsRequestSchema, conveyorCapabilitySchema, GetConnectionContextRequestSchema, VerifyConnectionRequestSchema, ListAccessibleSubprojectsRequestSchema, CreateProjectTaskRequestSchema, SetProjectTaskParentRequestSchema, UpdateProjectTaskRequestSchema, TransitionProjectTaskStatusRequestSchema, MoveProjectCardRequestSchema, PostToProjectTaskChatRequestSchema, GetProjectTaskCliRequestSchema, GetProjectTaskSessionsRequestSchema, QueryProjectGcpLogsRequestSchema, QueryProjectGrafanaLogsRequestSchema, driveFileNameSchema, DRIVE_MAX_CONTENT_CHARS, ListProjectDriveFilesRequestSchema, ReadProjectDriveFileRequestSchema, CreateProjectDriveFileRequestSchema, UpdateProjectDriveFileRequestSchema, DeleteProjectDriveFileRequestSchema, CreateProjectDriveFolderRequestSchema, StartProjectBuildRequestSchema, StopProjectBuildRequestSchema, StartProjectWorkspaceRequestSchema, StopProjectWorkspaceRequestSchema, ListMyLiveSessionsRequestSchema, ListProjectSessionGroupsRequestSchema, ListMyLiveSessionsAcrossProjectsRequestSchema, ListSessionGroupsAcrossProjectsRequestSchema, GetProjectAvailableTuisRequestSchema, StartAdhocSessionRequestSchema, StopAdhocSessionRequestSchema, ResumeAdhocSessionRequestSchema, RefreshCodingAgentKeyUsageRequestSchema, ListKeysToProbeRequestSchema, CreateProjectReleaseRequestSchema, AddTasksToProjectReleaseRequestSchema, ApproveProjectMergePRRequestSchema, ListProjectSubtasksRequestSchema, CreateProjectSubtaskRequestSchema, UpdateProjectSubtaskRequestSchema, DeleteProjectSubtaskRequestSchema, GetProjectTaskChatRequestSchema, AddProjectTaskDependencyRequestSchema, RemoveProjectTaskDependencyRequestSchema, VoteProjectSuggestionRequestSchema, GetProjectTaskDependenciesRequestSchema, CreateProjectPullRequestRequestSchema, ListProjectMembersRequestSchema, AddProjectTaskReviewerRequestSchema, RemoveProjectTaskReviewerRequestSchema, ListProjectManualTestsRequestSchema, QueryProjectManualTestsRequestSchema, SetProjectManualTestsRequestSchema, EditProjectManualTestRequestSchema, RemoveProjectManualTestRequestSchema, ApproveProjectManualTestRequestSchema, RejectProjectManualTestRequestSchema, CreateProjectSuggestionRequestSchema, ListProjectChannelsRequestSchema, READ_CHANNEL_MESSAGES_MAX_LIMIT, ReadChannelMessagesRequestSchema, POST_CHANNEL_MESSAGE_MAX_CHARS, PostChannelMessageRequestSchema, GET_ANALYTICS_SUMMARY_MAX_RANGE_DAYS, GetProjectAnalyticsSummaryRequestSchema, ListProjectTaskFilesRequestSchema, GetProjectAttachmentRequestSchema, DeleteProjectTaskFileRequestSchema, SetProjectTaskFileShowcaseRequestSchema, RequestProjectFileUploadRequestSchema, ConfirmProjectFileUploadRequestSchema, RequestWorkspaceRecycleRequestSchema, ReportApiOutageRequestSchema, CONTEXT_LINK_LOCATOR_MAX, TAG_DESCRIPTION_MAX, TAG_OVERVIEW_MAX, TAG_REASON_MAX, ProjectTagContextPathSchema, hexColor, overviewPathSchema, CreateProjectTagRequestSchema, UpdateProjectTagRequestSchema, PostToProjectChatRequestSchema, StartTagAuditRequestSchema, StartTaskAuditRequestSchema, GetActiveAuditSessionsRequestSchema, ReportTaskAuditResultRequestSchema, GetTaskAuditsRequestSchema, GetTaskAuditRequestSchema, GetTaskAuditAggregatesRequestSchema, DeleteTaskAuditRequestSchema, MarkInitialPromptSubmittedRequestSchema, PERSONAL_CLUSTER_EVENTS, PERSONAL_CLUSTER_HEARTBEAT_INTERVAL_MS, DECISION_STATUS_LABELS, DECISION_OPTION_KINDS, DECISION_VOTE_SOURCES, DECISION_OPTIONS_MIN, DECISION_OPTIONS_MAX, DECISION_OPTION_LABEL_MAX, DECISION_QUESTION_MAX, DECISION_RESOLUTION_MAX, DECISION_VOTE_REASON_MAX, DECISION_DEADLINE_MIN_HOURS, DECISION_DEADLINE_MAX_DAYS, DECISION_DEADLINE_MAX_HOURS, DECISION_LIST_MAX_LIMIT, decisionIdSchema, optionInputSchema, optionsArraySchema, CreateDecisionRequestSchema, VoteDecisionRequestSchema, RemoveDecisionVoteRequestSchema, ResolveDecisionRequestSchema, CancelDecisionRequestSchema, BulkDecisionIdsRequestSchema, ReopenDecisionRequestSchema, UpdateDecisionRequestSchema, GetDecisionRequestSchema, ListDecisionsRequestSchema, optionInput, optionsInput, deadlineHours, CreateProjectDecisionRequestSchema, ListProjectDecisionsRequestSchema, GetProjectDecisionRequestSchema, VoteProjectDecisionRequestSchema, ResolveProjectDecisionRequestSchema, UpdateProjectDecisionRequestSchema, EVALUATE_MAX_QUESTIONS, EVALUATE_MAX_ITEMS, EVALUATE_ITEM_MAX_CHARS, EVALUATE_REQUEST_MAX_CHARS, TEXT_MAX, QUESTION_KEY, EvaluateQuestionSchema, EvaluateQuestionsRequestSchema, MEETING_CHECKLIST_TITLE_MAX, MEETING_TRANSCRIPT_MAX_CHARS, MEETING_TITLE_MAX, MEETING_OCCURRED_AT_MIN_YEAR, MEETING_OCCURRED_AT_MAX_FUTURE_MS, OCCURRED_AT_RANGE_MESSAGE, MeetingOccurredAtSchema, CreateMeetingFromTranscriptRequestSchema, GetMeetingRequestSchema, UpdateMeetingRequestSchema, RegenerateMeetingSummaryRequestSchema, DeleteMeetingRequestSchema, checklistTitle, ListMeetingChecklistRequestSchema, AddMeetingChecklistItemsRequestSchema, UpdateMeetingChecklistItemRequestSchema, DeleteMeetingChecklistItemRequestSchema, SetMeetingChecklistItemCheckedRequestSchema, ListMeetingsRequestSchema, ReadMeetingTranscriptRequestSchema, MEETING_SUMMARY_MAX_CHARS, AddProjectMeetingChecklistItemsRequestSchema, CheckProjectMeetingChecklistItemRequestSchema, EditProjectMeetingChecklistItemRequestSchema, RemoveProjectMeetingChecklistItemRequestSchema, CreateProjectMeetingRequestSchema, UpdateProjectMeetingRequestSchema, TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT, AGENT_CHAT_HISTORY_FETCH_LIMIT, ANTHROPIC_CATALOG, CLAUDESPACE_WORKLOAD_LABEL, CLAUDESPACE_WORKLOAD_VALUE, CONVEYOR_POD_SELECTOR, CONTAINERD_K8S_NAMESPACE, CONTAINERD_PIN_LABEL, CONVEYOR_CONTENT_HASH_LABEL, VM_IMPORT_DISCOVERY_DIR, LOCAL_BAKE_ROOT_COMMANDS, PERSONAL_LOCAL_REGISTRY_NAMESPACE, PERSONAL_LOCAL_REGISTRY_NAME, PERSONAL_LOCAL_REGISTRY_PORT, PERSONAL_LOCAL_REGISTRY_HOST, PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT, BAKE_COMPONENT_LABEL, BAKE_COMPONENT_PREBAKE, BAKE_COMPONENT_STATE_BAKE, BAKE_COMPONENT_VALUES, PERSONAL_BAKE_NAMESPACE, MS_PER_DAY, POSTGRES_ENV, FIREBASE_EMULATOR_COMMAND, FIREBASE_EMULATOR_ENV, CATALOG, SERVICE_DEFINITIONS, SUPPORTED_CLAUDESPACE_DEPS, STATEFUL_BAKE_DEPS, MIRRORABLE_SIDECARS, LEVELS_PER_BAND, PRESTIGE_TIERS, TOP_PRESTIGE_BAND, CARD_TYPE_SURFACE, surfaceTypes, BOARD_CARD_TYPES, REPORT_CARD_TYPES, DECISION_CARD_TYPES;
|
|
4560
|
+
function relayAllowanceGiB(relaySlots = BASE_RELAY_SLOTS) {
|
|
4561
|
+
return Math.max(0, relaySlots - BASE_RELAY_SLOTS) * RELAY_RSS_ALLOWANCE_MIB / 1024;
|
|
4562
|
+
}
|
|
4563
|
+
function vmHostMemoryGiB(guestGiB, relaySlots = BASE_RELAY_SLOTS) {
|
|
4564
|
+
return guestGiB + Math.max(1, guestGiB / 4) + relayAllowanceGiB(relaySlots);
|
|
4565
|
+
}
|
|
4566
|
+
function maximumGuestMemoryGiBWithin(budgetGiB, relaySlots = BASE_RELAY_SLOTS) {
|
|
4567
|
+
const guestBudget = budgetGiB - relayAllowanceGiB(relaySlots);
|
|
4568
|
+
if (!Number.isFinite(guestBudget) || guestBudget <= 0) return 0;
|
|
4569
|
+
return Math.max(0, Math.floor(Math.min(guestBudget - 1, guestBudget / 1.25)));
|
|
4570
|
+
}
|
|
4571
|
+
function vmMemoryBudgetGiB(hostMemoryGiB) {
|
|
4572
|
+
return hostMemoryGiB * VM_HOST_MEMORY_SHARE;
|
|
4573
|
+
}
|
|
4574
|
+
function vmCpuBudget(hostCpus) {
|
|
4575
|
+
return Math.max(1, hostCpus - 1);
|
|
4576
|
+
}
|
|
4577
|
+
function recommendedVmSize(hostMemoryGiB, hostCpus) {
|
|
4578
|
+
return {
|
|
4579
|
+
cpus: defaultCpus(hostCpus),
|
|
4580
|
+
memoryGiB: Math.min(
|
|
4581
|
+
VM_DEFAULT_MEMORY_CAP_GIB,
|
|
4582
|
+
maximumGuestMemoryGiBWithin(vmMemoryBudgetGiB(hostMemoryGiB))
|
|
4583
|
+
)
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
function defaultCpus(hostCpus) {
|
|
4587
|
+
if (!Number.isFinite(hostCpus) || hostCpus < 1) return 0;
|
|
4588
|
+
const cores = Math.floor(hostCpus);
|
|
4589
|
+
return Math.min(VM_DEFAULT_CPU_CAP, vmCpuBudget(cores), Math.max(2, cores - 2));
|
|
4590
|
+
}
|
|
4591
|
+
function podsThatFit(size) {
|
|
4592
|
+
const memory = size.memoryGiB - GUEST_SYSTEM_RESERVE_GIB;
|
|
4593
|
+
const cpu = size.cpus - GUEST_SYSTEM_RESERVE_CPU;
|
|
4594
|
+
const fit = (preset) => Math.max(0, Math.floor(Math.min(memory / preset.memory, cpu / preset.cpu) + 1e-9));
|
|
4595
|
+
return { medium: fit(POD_SIZE_PRESETS.medium), small: fit(POD_SIZE_PRESETS.small) };
|
|
4596
|
+
}
|
|
4597
|
+
function smallestVmFor(pod) {
|
|
4598
|
+
const preset = POD_SIZE_PRESETS[pod];
|
|
4599
|
+
return {
|
|
4600
|
+
cpus: Math.ceil(preset.cpu + GUEST_SYSTEM_RESERVE_CPU),
|
|
4601
|
+
memoryGiB: Math.ceil(preset.memory + GUEST_SYSTEM_RESERVE_GIB)
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
var ACHIEVEMENT_RARITIES, RISK_LEVELS, riskLevelSchema, DEFAULT_RISK_LEVELS, LEVEL_BY_VALUE, ACTIVE_WORK_STATUSES, IDENTIFIED_WORK_STATUSES, DEFAULT_TASK_STATUS_COLOR, DEFAULT_TASK_STATUS_COLOR_INT, MAX_FILE_SIZE_BYTES, MAX_FILE_TAGS, MAX_FILE_TAG_LENGTH, ALLOWED_FILE_MIME_TYPES, ALLOWED_FILE_ACCEPT, EMBED_THRESHOLD_IMAGES, EMBED_THRESHOLD_TEXT, IDLE_HEARTBEAT_MS, siteAssetExtension, RequestSiteAssetUploadRequestSchema, ListSiteAssetTargetsRequestSchema, TurnEndToolCallSchema, AgentTurnUsageSchema, KnownAgentEventSchema, AgentEventSchema, PtyChatAtSchema, PtyChatTurnUsageSchema, PtyChatTurnFileSchema, PTY_CHAT_TURN_FILES_MAX, PtyChatEventPayloadSchema, PtyChatEventRequestSchema, PtyChatAttachRequestSchema, PTY_EFFORT_LEVELS, SetPtyModelEffortRequestSchema, cardDescription, AgentHeartbeatSchema, CreatePRInputSchema, PostToChatInputSchema, GetTaskContextRequestSchema, GetChatMessagesRequestSchema, GetTaskFilesRequestSchema, GetTaskFileRequestSchema, DeleteTaskFileRequestSchema, SetTaskFileShowcaseRequestSchema, GetTaskRequestSchema, GetCliHistoryRequestSchema, ListSubtasksRequestSchema, GetDependenciesRequestSchema, GetSuggestionsRequestSchema, ListManualTestsRequestSchema, QueryManualTestsRequestSchema, CreatePullRequestRequestSchema, RequestFileUploadRequestSchema, ConfirmFileUploadRequestSchema, UpdateTaskStatusRequestSchema, StoreSessionIdRequestSchema, SetManualTestsRequestSchema, EditManualTestRequestSchema, RemoveManualTestRequestSchema, ApproveManualTestRequestSchema, RejectManualTestRequestSchema, SessionStartRequestSchema, SessionStopRequestSchema, EndReviewSessionRequestSchema, ConnectAgentRequestSchema, ReportAgentStatusRequestSchema, NotifyAgentVersionRequestSchema, DiscoveredPortSchema, ReportDiscoveredPortsRequestSchema, CreateSubtaskRequestSchema, UpdateSubtaskRequestSchema, DeleteSubtaskRequestSchema, SetSubtaskParentRequestSchema, GetTaskPropertiesRequestSchema, UpdateTaskFieldsRequestSchema, UpdateTaskPropertiesRequestSchema, ListIconsRequestSchema, GenerateTaskIconRequestSchema, SearchFaIconsRequestSchema, PickFaIconRequestSchema, CreateFollowUpTaskRequestSchema, AddDependencyRequestSchema, RemoveDependencyRequestSchema, CreateSuggestionRequestSchema, VoteSuggestionRequestSchema, TriggerIdentificationRequestSchema, HandoffToImplementerRequestSchema, SubmitCodeReviewResultRequestSchema, CycleCodingAgentKeyRequestSchema, PostChildChatMessageRequestSchema, UpdateChildStatusRequestSchema, GetAgentStatusRequestSchema, GetUiCliHistoryRequestSchema, GetActivePtySessionRequestSchema, ListActivePtySessionsRequestSchema, SendSoftStopRequestSchema, StopTaskSessionRequestSchema, FlushTaskQueueRequestSchema, CancelTaskQueuedMessageRequestSchema, FlushSingleQueuedMessageRequestSchema, AnswerAgentQuestionRequestSchema, ClearAgentTodosRequestSchema, AgentQuestionOptionSchema, AgentQuestionSchema, AskUserQuestionRequestSchema, PostAgentMessageRequestSchema, EmitAgentEventRequestSchema, RefreshGithubTokenRequestSchema, ReportCredentialFailureRequestSchema, ReportReviewSpawnFailureRequestSchema, ReportBuilderSpawnFailureRequestSchema, SpawnTaskSessionRequestSchema, StartCodeReviewRequestSchema, StopCodeReviewRequestSchema, ReportSessionSpawnFailureRequestSchema, RefreshGithubTokenResponseSchema, PTY_FRAME_MAX_CHARS, PTY_MAX_DIMENSION, PtyOutputRequestSchema, PtyEndedRequestSchema, PtyInputRequestSchema, PtyResizeRequestSchema, PtyAttachRequestSchema, ReportPtyStreamRequestSchema, ReportPtyInputRequestSchema, GetPtyStreamEndpointRequestSchema, CreatePRResponseSchema, PostToChatResponseSchema, UpdateTaskStatusResponseSchema, StoreSessionIdResponseSchema, HeartbeatResponseSchema, SessionStartResponseSchema, SessionStopResponseSchema, DeleteSubtaskResponseSchema, ParkOnCheckResultRequestSchema, GIT_BRANCH_NAME_MAX, GIT_BRANCH_NAME_MESSAGE, ALLOWED_REF, cardDescription2, ListAccessibleProjectsRequestSchema, ListProjectTasksRequestSchema, GetProjectTaskRequestSchema, SearchProjectTasksRequestSchema, ListProjectTagsRequestSchema, GetProjectTagRequestSchema, ListProjectTagAttachmentsRequestSchema, SetProjectFileTagsRequestSchema, GetProjectSummaryRequestSchema, GetProjectOnboardingStatusRequestSchema, GetProjectOnboardingStepRequestSchema, GetProjectConnectUrlsRequestSchema, conveyorCapabilitySchema, GetConnectionContextRequestSchema, VerifyConnectionRequestSchema, ListAccessibleSubprojectsRequestSchema, CreateProjectTaskRequestSchema, SetProjectTaskParentRequestSchema, UpdateProjectTaskRequestSchema, TransitionProjectTaskStatusRequestSchema, MoveProjectCardRequestSchema, PostToProjectTaskChatRequestSchema, GetProjectTaskCliRequestSchema, GetProjectTaskSessionsRequestSchema, QueryProjectGcpLogsRequestSchema, QueryProjectGrafanaLogsRequestSchema, driveFileNameSchema, DRIVE_MAX_CONTENT_CHARS, ListProjectDriveFilesRequestSchema, ReadProjectDriveFileRequestSchema, CreateProjectDriveFileRequestSchema, UpdateProjectDriveFileRequestSchema, DeleteProjectDriveFileRequestSchema, CreateProjectDriveFolderRequestSchema, StartProjectBuildRequestSchema, StopProjectBuildRequestSchema, StartProjectWorkspaceRequestSchema, StopProjectWorkspaceRequestSchema, ListMyLiveSessionsRequestSchema, ListProjectSessionGroupsRequestSchema, ListMyLiveSessionsAcrossProjectsRequestSchema, ListSessionGroupsAcrossProjectsRequestSchema, GetProjectAvailableTuisRequestSchema, StartAdhocSessionRequestSchema, StopAdhocSessionRequestSchema, ResumeAdhocSessionRequestSchema, RefreshCodingAgentKeyUsageRequestSchema, ListKeysToProbeRequestSchema, CreateProjectReleaseRequestSchema, AddTasksToProjectReleaseRequestSchema, ApproveProjectMergePRRequestSchema, ListProjectSubtasksRequestSchema, CreateProjectSubtaskRequestSchema, UpdateProjectSubtaskRequestSchema, DeleteProjectSubtaskRequestSchema, GetProjectTaskChatRequestSchema, AddProjectTaskDependencyRequestSchema, RemoveProjectTaskDependencyRequestSchema, VoteProjectSuggestionRequestSchema, GetProjectTaskDependenciesRequestSchema, CreateProjectPullRequestRequestSchema, ListProjectMembersRequestSchema, AddProjectTaskReviewerRequestSchema, RemoveProjectTaskReviewerRequestSchema, ListProjectManualTestsRequestSchema, QueryProjectManualTestsRequestSchema, SetProjectManualTestsRequestSchema, EditProjectManualTestRequestSchema, RemoveProjectManualTestRequestSchema, ApproveProjectManualTestRequestSchema, RejectProjectManualTestRequestSchema, CreateProjectSuggestionRequestSchema, ListProjectChannelsRequestSchema, READ_CHANNEL_MESSAGES_MAX_LIMIT, ReadChannelMessagesRequestSchema, POST_CHANNEL_MESSAGE_MAX_CHARS, PostChannelMessageRequestSchema, GET_ANALYTICS_SUMMARY_MAX_RANGE_DAYS, GetProjectAnalyticsSummaryRequestSchema, ListProjectTaskFilesRequestSchema, GetProjectAttachmentRequestSchema, DeleteProjectTaskFileRequestSchema, SetProjectTaskFileShowcaseRequestSchema, RequestProjectFileUploadRequestSchema, ConfirmProjectFileUploadRequestSchema, RequestWorkspaceRecycleRequestSchema, ReportApiOutageRequestSchema, CONTEXT_LINK_LOCATOR_MAX, TAG_DESCRIPTION_MAX, TAG_OVERVIEW_MAX, TAG_REASON_MAX, ProjectTagContextPathSchema, hexColor, overviewPathSchema, CreateProjectTagRequestSchema, UpdateProjectTagRequestSchema, PostToProjectChatRequestSchema, StartTagAuditRequestSchema, StartTaskAuditRequestSchema, GetActiveAuditSessionsRequestSchema, ReportTaskAuditResultRequestSchema, GetTaskAuditsRequestSchema, GetTaskAuditRequestSchema, GetTaskAuditAggregatesRequestSchema, DeleteTaskAuditRequestSchema, MarkInitialPromptSubmittedRequestSchema, PERSONAL_CLUSTER_EVENTS, PERSONAL_CLUSTER_HEARTBEAT_INTERVAL_MS, DECISION_STATUS_LABELS, DECISION_OPTION_KINDS, DECISION_VOTE_SOURCES, DECISION_OPTIONS_MIN, DECISION_OPTIONS_MAX, DECISION_OPTION_LABEL_MAX, DECISION_QUESTION_MAX, DECISION_RESOLUTION_MAX, DECISION_VOTE_REASON_MAX, DECISION_DEADLINE_MIN_HOURS, DECISION_DEADLINE_MAX_DAYS, DECISION_DEADLINE_MAX_HOURS, DECISION_LIST_MAX_LIMIT, decisionIdSchema, optionInputSchema, optionsArraySchema, CreateDecisionRequestSchema, VoteDecisionRequestSchema, RemoveDecisionVoteRequestSchema, ResolveDecisionRequestSchema, CancelDecisionRequestSchema, BulkDecisionIdsRequestSchema, ReopenDecisionRequestSchema, UpdateDecisionRequestSchema, GetDecisionRequestSchema, ListDecisionsRequestSchema, optionInput, optionsInput, deadlineHours, CreateProjectDecisionRequestSchema, ListProjectDecisionsRequestSchema, GetProjectDecisionRequestSchema, VoteProjectDecisionRequestSchema, ResolveProjectDecisionRequestSchema, UpdateProjectDecisionRequestSchema, EVALUATE_MAX_QUESTIONS, EVALUATE_MAX_ITEMS, EVALUATE_ITEM_MAX_CHARS, EVALUATE_REQUEST_MAX_CHARS, TEXT_MAX, QUESTION_KEY, EvaluateQuestionSchema, EvaluateQuestionsRequestSchema, MEETING_CHECKLIST_TITLE_MAX, MEETING_TRANSCRIPT_MAX_CHARS, MEETING_TITLE_MAX, MEETING_OCCURRED_AT_MIN_YEAR, MEETING_OCCURRED_AT_MAX_FUTURE_MS, OCCURRED_AT_RANGE_MESSAGE, MeetingOccurredAtSchema, CreateMeetingFromTranscriptRequestSchema, GetMeetingRequestSchema, UpdateMeetingRequestSchema, RegenerateMeetingSummaryRequestSchema, DeleteMeetingRequestSchema, checklistTitle, ListMeetingChecklistRequestSchema, AddMeetingChecklistItemsRequestSchema, UpdateMeetingChecklistItemRequestSchema, DeleteMeetingChecklistItemRequestSchema, SetMeetingChecklistItemCheckedRequestSchema, ListMeetingsRequestSchema, ReadMeetingTranscriptRequestSchema, MEETING_SUMMARY_MAX_CHARS, AddProjectMeetingChecklistItemsRequestSchema, CheckProjectMeetingChecklistItemRequestSchema, EditProjectMeetingChecklistItemRequestSchema, RemoveProjectMeetingChecklistItemRequestSchema, CreateProjectMeetingRequestSchema, UpdateProjectMeetingRequestSchema, TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT, AGENT_CHAT_HISTORY_FETCH_LIMIT, ANTHROPIC_CATALOG, PROJECT_ARCHIVE_RETENTION_DAYS, RETENTION_MS, CLAUDESPACE_WORKLOAD_LABEL, CLAUDESPACE_WORKLOAD_VALUE, CONVEYOR_POD_SELECTOR, CONTAINERD_K8S_NAMESPACE, CONTAINERD_PIN_LABEL, CONVEYOR_CONTENT_HASH_LABEL, VM_IMPORT_DISCOVERY_DIR, LOCAL_BAKE_ROOT_COMMANDS, PERSONAL_LOCAL_REGISTRY_NAMESPACE, PERSONAL_LOCAL_REGISTRY_NAME, PERSONAL_LOCAL_REGISTRY_PORT, PERSONAL_LOCAL_REGISTRY_HOST, PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT, PERSONAL_LOCAL_REGISTRY_PUSH_SECRET, PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY, BAKE_COMPONENT_LABEL, BAKE_COMPONENT_PREBAKE, BAKE_COMPONENT_STATE_BAKE, BAKE_COMPONENT_VALUES, PERSONAL_BAKE_NAMESPACE, MS_PER_DAY, POSTGRES_ENV, FIREBASE_EMULATOR_COMMAND, FIREBASE_EMULATOR_ENV, CATALOG, SERVICE_DEFINITIONS, SUPPORTED_CLAUDESPACE_DEPS, STATEFUL_BAKE_DEPS, MIRRORABLE_SIDECARS, LEVELS_PER_BAND, PRESTIGE_TIERS, TOP_PRESTIGE_BAND, POD_SIZE_PRESETS, VM_HOST_MEMORY_SHARE, RELAY_RSS_ALLOWANCE_MIB, BASE_RELAY_SLOTS, VM_DEFAULT_MEMORY_CAP_GIB, VM_DEFAULT_CPU_CAP, GUEST_SYSTEM_RESERVE_GIB, GUEST_SYSTEM_RESERVE_CPU, CARD_TYPE_SURFACE, surfaceTypes, BOARD_CARD_TYPES, REPORT_CARD_TYPES, DECISION_CARD_TYPES;
|
|
4523
4605
|
var init_dist = __esm({
|
|
4524
4606
|
"../shared/dist/index.js"() {
|
|
4525
4607
|
"use strict";
|
|
4526
|
-
|
|
4608
|
+
init_chunk_SFSQ7NKP();
|
|
4609
|
+
init_zod();
|
|
4527
4610
|
init_zod();
|
|
4528
4611
|
init_zod();
|
|
4529
4612
|
init_zod();
|
|
@@ -4645,6 +4728,15 @@ var init_dist = __esm({
|
|
|
4645
4728
|
EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
|
|
4646
4729
|
EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
|
|
4647
4730
|
IDLE_HEARTBEAT_MS = 90 * 1e3;
|
|
4731
|
+
siteAssetExtension = external_exports.string().regex(/^[a-z0-9]{1,10}$/);
|
|
4732
|
+
RequestSiteAssetUploadRequestSchema = external_exports.object({
|
|
4733
|
+
projectId: external_exports.string(),
|
|
4734
|
+
siteId: external_exports.string().min(1).max(100),
|
|
4735
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
4736
|
+
ext: siteAssetExtension,
|
|
4737
|
+
sizeBytes: external_exports.number().int().positive()
|
|
4738
|
+
});
|
|
4739
|
+
ListSiteAssetTargetsRequestSchema = external_exports.object({ projectId: external_exports.string() });
|
|
4648
4740
|
TurnEndToolCallSchema = external_exports.object({
|
|
4649
4741
|
tool: external_exports.string(),
|
|
4650
4742
|
input: external_exports.string().optional(),
|
|
@@ -4936,7 +5028,9 @@ var init_dist = __esm({
|
|
|
4936
5028
|
GetTaskFileRequestSchema = external_exports.object({
|
|
4937
5029
|
sessionId: external_exports.string(),
|
|
4938
5030
|
fileId: external_exports.string(),
|
|
4939
|
-
taskId: external_exports.string().optional()
|
|
5031
|
+
taskId: external_exports.string().optional(),
|
|
5032
|
+
/** Ask for a short-lived `agentDownloadUrl` when the bytes are not inlined. */
|
|
5033
|
+
agentDownload: external_exports.boolean().optional()
|
|
4940
5034
|
});
|
|
4941
5035
|
DeleteTaskFileRequestSchema = external_exports.object({
|
|
4942
5036
|
sessionId: external_exports.string(),
|
|
@@ -5076,10 +5170,6 @@ var init_dist = __esm({
|
|
|
5076
5170
|
sessionId: external_exports.string(),
|
|
5077
5171
|
ports: external_exports.array(DiscoveredPortSchema).max(64)
|
|
5078
5172
|
});
|
|
5079
|
-
ReportBootMilestoneRequestSchema = external_exports.object({
|
|
5080
|
-
sessionId: external_exports.string(),
|
|
5081
|
-
key: external_exports.string().max(64)
|
|
5082
|
-
});
|
|
5083
5173
|
CreateSubtaskRequestSchema = external_exports.object({
|
|
5084
5174
|
sessionId: external_exports.string(),
|
|
5085
5175
|
title: external_exports.string().min(1),
|
|
@@ -6532,6 +6622,8 @@ var init_dist = __esm({
|
|
|
6532
6622
|
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
6533
6623
|
anthropicEntry(FABLE_MODEL, "Fable 5.1", 10, 50)
|
|
6534
6624
|
];
|
|
6625
|
+
PROJECT_ARCHIVE_RETENTION_DAYS = 30;
|
|
6626
|
+
RETENTION_MS = PROJECT_ARCHIVE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
6535
6627
|
CLAUDESPACE_WORKLOAD_LABEL = "rc-workload";
|
|
6536
6628
|
CLAUDESPACE_WORKLOAD_VALUE = "claudespace";
|
|
6537
6629
|
CONVEYOR_POD_SELECTOR = `${CLAUDESPACE_WORKLOAD_LABEL}=${CLAUDESPACE_WORKLOAD_VALUE}`;
|
|
@@ -6556,6 +6648,8 @@ var init_dist = __esm({
|
|
|
6556
6648
|
PERSONAL_LOCAL_REGISTRY_PORT = 5e3;
|
|
6557
6649
|
PERSONAL_LOCAL_REGISTRY_HOST = `${PERSONAL_LOCAL_REGISTRY_NAME}.${PERSONAL_LOCAL_REGISTRY_NAMESPACE}.svc.cluster.local:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6558
6650
|
PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT = `http://127.0.0.1:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6651
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET = "conveyor-registry-push";
|
|
6652
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY = "config.json";
|
|
6559
6653
|
BAKE_COMPONENT_LABEL = "claudespace.rallycry.com/component";
|
|
6560
6654
|
BAKE_COMPONENT_PREBAKE = "prebake";
|
|
6561
6655
|
BAKE_COMPONENT_STATE_BAKE = "state-bake";
|
|
@@ -6862,6 +6956,18 @@ exec firebase emulators:start --only=auth --project=rally-cry-dev`
|
|
|
6862
6956
|
minLevel: (index + 1) * LEVELS_PER_BAND
|
|
6863
6957
|
}));
|
|
6864
6958
|
TOP_PRESTIGE_BAND = PRESTIGE_TIERS.length;
|
|
6959
|
+
POD_SIZE_PRESETS = {
|
|
6960
|
+
small: { cpu: 1.25, memory: 7, cpuLimit: 3 },
|
|
6961
|
+
medium: { cpu: 2.25, memory: 14, cpuLimit: 7 },
|
|
6962
|
+
large: { cpu: 2.4, memory: 15.5, cpuLimit: 9.5 }
|
|
6963
|
+
};
|
|
6964
|
+
VM_HOST_MEMORY_SHARE = 0.75;
|
|
6965
|
+
RELAY_RSS_ALLOWANCE_MIB = 128;
|
|
6966
|
+
BASE_RELAY_SLOTS = 16;
|
|
6967
|
+
VM_DEFAULT_MEMORY_CAP_GIB = 32;
|
|
6968
|
+
VM_DEFAULT_CPU_CAP = 8;
|
|
6969
|
+
GUEST_SYSTEM_RESERVE_GIB = 1.5;
|
|
6970
|
+
GUEST_SYSTEM_RESERVE_CPU = 0.25;
|
|
6865
6971
|
CARD_TYPE_SURFACE = {
|
|
6866
6972
|
task: "board",
|
|
6867
6973
|
chat: "board",
|
|
@@ -7519,23 +7625,10 @@ var init_host_state = __esm({
|
|
|
7519
7625
|
});
|
|
7520
7626
|
|
|
7521
7627
|
// src/vm/memory-budget.ts
|
|
7522
|
-
function relayAllowanceGiB(relaySlots = BASE_RELAY_SLOTS) {
|
|
7523
|
-
return Math.max(0, relaySlots - BASE_RELAY_SLOTS) * RELAY_RSS_ALLOWANCE_MIB / 1024;
|
|
7524
|
-
}
|
|
7525
|
-
function vmHostMemoryGiB(guestGiB, relaySlots = BASE_RELAY_SLOTS) {
|
|
7526
|
-
return guestGiB + Math.max(1, guestGiB / 4) + relayAllowanceGiB(relaySlots);
|
|
7527
|
-
}
|
|
7528
|
-
function maximumGuestMemoryGiBWithin(budgetGiB, relaySlots = BASE_RELAY_SLOTS) {
|
|
7529
|
-
const guestBudget = budgetGiB - relayAllowanceGiB(relaySlots);
|
|
7530
|
-
if (!Number.isFinite(guestBudget) || guestBudget <= 0) return 0;
|
|
7531
|
-
return Math.max(0, Math.floor(Math.min(guestBudget - 1, guestBudget / 1.25)));
|
|
7532
|
-
}
|
|
7533
|
-
var RELAY_RSS_ALLOWANCE_MIB, BASE_RELAY_SLOTS;
|
|
7534
7628
|
var init_memory_budget = __esm({
|
|
7535
7629
|
"src/vm/memory-budget.ts"() {
|
|
7536
7630
|
"use strict";
|
|
7537
|
-
|
|
7538
|
-
BASE_RELAY_SLOTS = 16;
|
|
7631
|
+
init_dist();
|
|
7539
7632
|
}
|
|
7540
7633
|
});
|
|
7541
7634
|
|
|
@@ -7543,10 +7636,10 @@ var init_memory_budget = __esm({
|
|
|
7543
7636
|
import { cpus, freemem, totalmem } from "os";
|
|
7544
7637
|
import { stringify as stringify2 } from "yaml";
|
|
7545
7638
|
function cpuBudget(hostCpus) {
|
|
7546
|
-
return
|
|
7639
|
+
return vmCpuBudget(hostCpus);
|
|
7547
7640
|
}
|
|
7548
7641
|
function memoryBudgetGiB(hostBytes) {
|
|
7549
|
-
return hostBytes / GIB2
|
|
7642
|
+
return vmMemoryBudgetGiB(hostBytes / GIB2);
|
|
7550
7643
|
}
|
|
7551
7644
|
function reservedHostMemoryGiB(allocations2) {
|
|
7552
7645
|
return allocations2.reduce(
|
|
@@ -7574,7 +7667,7 @@ function validateResources(resources, host = { cpus: cpus().length, memory: tota
|
|
|
7574
7667
|
);
|
|
7575
7668
|
}
|
|
7576
7669
|
const hostGiB = vmHostMemoryGiB(memoryGiB, relaySlots) + reservedHostMemoryGiB(others);
|
|
7577
|
-
if (!Number.isInteger(memoryGiB) || memoryGiB < 2 || hostGiB
|
|
7670
|
+
if (!Number.isInteger(memoryGiB) || memoryGiB < 2 || hostGiB > memoryBudgetGiB(host.memory)) {
|
|
7578
7671
|
throw new Error(
|
|
7579
7672
|
`VM memory must be at least 2 GiB and leave 25% of host memory available, including QEMU and relay overhead.${sharedTotals(others, `${hostGiB.toFixed(1)} of ${memoryBudgetGiB(host.memory).toFixed(1)} GiB of host memory`)}${relayNote(relaySlots)}`
|
|
7580
7673
|
);
|
|
@@ -7616,20 +7709,6 @@ function renderVmTemplate(arch, resources, dnsScript, firmware) {
|
|
|
7616
7709
|
provision: [{ mode: "system", script: dnsScript }]
|
|
7617
7710
|
});
|
|
7618
7711
|
}
|
|
7619
|
-
function parseInstallUrl(value) {
|
|
7620
|
-
const url = new URL(value.trim());
|
|
7621
|
-
if (url.protocol !== "https:" || url.username || url.password || url.hash || url.port) {
|
|
7622
|
-
throw new Error(
|
|
7623
|
-
"Enrollment requires an HTTPS install URL on port 443 without userinfo or fragments."
|
|
7624
|
-
);
|
|
7625
|
-
}
|
|
7626
|
-
if (!/^\/api\/personal-clusters\/[a-z0-9]+\/install\.sh$/.test(url.pathname)) {
|
|
7627
|
-
throw new Error("Use the install URL from Personal Compute settings.");
|
|
7628
|
-
}
|
|
7629
|
-
if (!url.searchParams.get("token"))
|
|
7630
|
-
throw new Error("Install URL is missing its enrollment token.");
|
|
7631
|
-
return url;
|
|
7632
|
-
}
|
|
7633
7712
|
function validateStartupMemory(memoryGiB, hostBytes = totalmem(), others = [], relaySlots) {
|
|
7634
7713
|
const budgetGiB = memoryBudgetGiB(hostBytes) - reservedHostMemoryGiB(others);
|
|
7635
7714
|
if (memoryGiB > maximumGuestMemoryGiBWithin(budgetGiB, relaySlots)) {
|
|
@@ -7642,6 +7721,7 @@ var GIB2, IMAGES;
|
|
|
7642
7721
|
var init_config = __esm({
|
|
7643
7722
|
"src/vm/config.ts"() {
|
|
7644
7723
|
"use strict";
|
|
7724
|
+
init_dist();
|
|
7645
7725
|
init_memory_budget();
|
|
7646
7726
|
init_instance_name();
|
|
7647
7727
|
GIB2 = 1024 ** 3;
|
|
@@ -7754,7 +7834,7 @@ var init_host_env = __esm({
|
|
|
7754
7834
|
});
|
|
7755
7835
|
|
|
7756
7836
|
// src/vm/endpoints.ts
|
|
7757
|
-
var CONTROL_PLANE_HOSTS, DEFAULT_ENDPOINTS,
|
|
7837
|
+
var CONTROL_PLANE_HOSTS, DEFAULT_ENDPOINTS, GUEST_CLI, TUNNEL_TCP;
|
|
7758
7838
|
var init_endpoints = __esm({
|
|
7759
7839
|
"src/vm/endpoints.ts"() {
|
|
7760
7840
|
"use strict";
|
|
@@ -7815,15 +7895,6 @@ var init_endpoints = __esm({
|
|
|
7815
7895
|
host,
|
|
7816
7896
|
ports: host.endsWith(".ubuntu.com") || host === "deb.debian.org" ? [80, 443] : [443]
|
|
7817
7897
|
}));
|
|
7818
|
-
GUEST_ENROLL = `set -euo pipefail
|
|
7819
|
-
IFS= read -r install_url
|
|
7820
|
-
umask 077
|
|
7821
|
-
installer=$(mktemp /run/conveyor-installer.XXXXXX)
|
|
7822
|
-
trap 'rm -f "$installer"' EXIT
|
|
7823
|
-
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --max-time 120 "$install_url" -o "$installer"
|
|
7824
|
-
unset install_url
|
|
7825
|
-
bash "$installer"
|
|
7826
|
-
`;
|
|
7827
7898
|
GUEST_CLI = `set -euo pipefail
|
|
7828
7899
|
set -a
|
|
7829
7900
|
. /etc/conveyor/k3.env
|
|
@@ -8041,7 +8112,7 @@ var init_public_web = __esm({
|
|
|
8041
8112
|
// src/vm/relay.ts
|
|
8042
8113
|
import { kill } from "process";
|
|
8043
8114
|
import { execFileSync } from "child_process";
|
|
8044
|
-
import { lstatSync as lstatSync2, mkdirSync, readFileSync as
|
|
8115
|
+
import { lstatSync as lstatSync2, mkdirSync, readFileSync as readFileSync4, rmSync, writeFileSync } from "fs";
|
|
8045
8116
|
import { join as join3 } from "path";
|
|
8046
8117
|
import { Socket } from "net";
|
|
8047
8118
|
function validPid2(value) {
|
|
@@ -8082,7 +8153,7 @@ function stoppedSlotRecord(root, slot) {
|
|
|
8082
8153
|
}
|
|
8083
8154
|
try {
|
|
8084
8155
|
const record2 = JSON.parse(
|
|
8085
|
-
|
|
8156
|
+
readFileSync4(join3(path, "owner.json"), "utf8")
|
|
8086
8157
|
);
|
|
8087
8158
|
if (!validPid2(record2.pid)) throw new Error("missing process ID");
|
|
8088
8159
|
return record2;
|
|
@@ -8153,7 +8224,7 @@ var init_host_packages = __esm({
|
|
|
8153
8224
|
"firmware-x64": "ovmf",
|
|
8154
8225
|
"firmware-arm64": "qemu-efi-aarch64"
|
|
8155
8226
|
};
|
|
8156
|
-
LIMA_INSTALL_HINT = "Lima 2.2+ (2.x) is not in apt: download lima-<version>-Linux-<x86_64|aarch64>.tar.gz from https://github.com/lima-vm/lima/releases and run
|
|
8227
|
+
LIMA_INSTALL_HINT = "Lima 2.2+ (2.x) is not in apt: download lima-<version>-Linux-<x86_64|aarch64>.tar.gz from https://github.com/lima-vm/lima/releases and run tar -C ~/.local -xzf <file> (preferred) or sudo tar --no-same-owner --no-same-permissions -C /usr/local -xzf <file>; a plain sudo tar hands /usr/local/bin to the uid that built the archive. The CLI searches ~/.local/bin, /usr/local/bin, and /usr/bin, not /home/linuxbrew.";
|
|
8157
8228
|
}
|
|
8158
8229
|
});
|
|
8159
8230
|
|
|
@@ -8248,13 +8319,25 @@ async function findFirmware(qemu, arch, host, os) {
|
|
|
8248
8319
|
const hint = packageHint(arch === "arm64" ? "firmware-arm64" : "firmware-x64", os);
|
|
8249
8320
|
throw new Error(`Install QEMU UEFI firmware before creating the VM${hint ? `: ${hint}` : "."}`);
|
|
8250
8321
|
}
|
|
8251
|
-
async function
|
|
8322
|
+
async function warnOnForeignPrefixOwner(exec, log2) {
|
|
8323
|
+
const result = await exec("/usr/bin/stat", ["-c", "%u %U %n", ...ROOT_OWNED_PREFIX]).catch(
|
|
8324
|
+
() => void 0
|
|
8325
|
+
);
|
|
8326
|
+
const foreign = (result?.stdout ?? "").split("\n").map((line) => /^(\d+) (\S+) (\/\S*)$/.exec(line.trim())).filter((match) => !!match && match[1] !== "0").map(([, uid, name2, path]) => `${path} (uid ${uid}${name2 === "UNKNOWN" ? "" : `, ${name2}`})`);
|
|
8327
|
+
if (foreign.length === 0) return;
|
|
8328
|
+
log2(
|
|
8329
|
+
`Warning: ${foreign.join(", ")} is not owned by root, so that account can replace programs root runs from /usr/local/bin. A sudo tar extraction without --no-same-owner causes this. Fix it with: sudo chown root:root ${ROOT_OWNED_PREFIX.join(" ")}`
|
|
8330
|
+
);
|
|
8331
|
+
}
|
|
8332
|
+
async function preflightHost(exec, paths, resources, host = realHost(), log2 = () => {
|
|
8333
|
+
}) {
|
|
8252
8334
|
if (!["darwin", "linux"].includes(host.platform) || host.uid === 0) {
|
|
8253
8335
|
throw new Error("Run the VM launcher as an ordinary user on macOS or Linux, without sudo.");
|
|
8254
8336
|
}
|
|
8255
8337
|
const arch = vmArch(host.arch);
|
|
8256
8338
|
const os = host.platform === "linux" ? await host.osRelease() : void 0;
|
|
8257
8339
|
if (host.platform === "linux") await assertKvm(host);
|
|
8340
|
+
if (host.platform === "linux") await warnOnForeignPrefixOwner(exec, log2);
|
|
8258
8341
|
const { lima, qemu, nc } = await locateTools(exec, paths, arch, host, os);
|
|
8259
8342
|
await assertLimaVersion(exec, paths, lima, host);
|
|
8260
8343
|
const acceleration = await runOrThrow(exec, qemu, ["-accel", "help"]);
|
|
@@ -8268,6 +8351,7 @@ async function preflightHost(exec, paths, resources, host = realHost()) {
|
|
|
8268
8351
|
const firmware = await findFirmware(qemu, arch, host, os);
|
|
8269
8352
|
return { arch, lima, qemu, nc, firmware };
|
|
8270
8353
|
}
|
|
8354
|
+
var ROOT_OWNED_PREFIX;
|
|
8271
8355
|
var init_preflight = __esm({
|
|
8272
8356
|
"src/vm/preflight.ts"() {
|
|
8273
8357
|
"use strict";
|
|
@@ -8275,16 +8359,17 @@ var init_preflight = __esm({
|
|
|
8275
8359
|
init_config();
|
|
8276
8360
|
init_host_env();
|
|
8277
8361
|
init_host_packages();
|
|
8362
|
+
ROOT_OWNED_PREFIX = ["/usr/local", "/usr/local/bin", "/usr/local/share"];
|
|
8278
8363
|
}
|
|
8279
8364
|
});
|
|
8280
8365
|
|
|
8281
8366
|
// src/vm/host-runtime.ts
|
|
8282
8367
|
import { readFile as readFile5 } from "fs/promises";
|
|
8283
|
-
import { createHash as
|
|
8368
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
8284
8369
|
import { join as join5 } from "path";
|
|
8285
8370
|
import { fileURLToPath } from "url";
|
|
8286
8371
|
function hashText(value) {
|
|
8287
|
-
return
|
|
8372
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
8288
8373
|
}
|
|
8289
8374
|
function newVmRunId() {
|
|
8290
8375
|
return randomUUID2();
|
|
@@ -8824,7 +8909,6 @@ __export(manager_exports, {
|
|
|
8824
8909
|
configureVm: () => configureVm,
|
|
8825
8910
|
createVm: () => createVm,
|
|
8826
8911
|
denyEndpoint: () => denyEndpoint,
|
|
8827
|
-
enrollVm: () => enrollVm,
|
|
8828
8912
|
enrollVmBootstrap: () => enrollVmBootstrap,
|
|
8829
8913
|
ensureVmDevicePublicKey: () => ensureVmDevicePublicKey,
|
|
8830
8914
|
internetMode: () => internetMode,
|
|
@@ -8850,16 +8934,15 @@ async function saveState(paths, state) {
|
|
|
8850
8934
|
await atomicPrivateWrite(paths.state, `${JSON.stringify(state, null, 2)}
|
|
8851
8935
|
`);
|
|
8852
8936
|
}
|
|
8853
|
-
async function createVm(ports, resources
|
|
8937
|
+
async function createVm(ports, resources) {
|
|
8854
8938
|
validateResources(resources, void 0, await ports.otherAllocations?.() ?? []);
|
|
8855
8939
|
const { paths, exec, log: log2 } = ports;
|
|
8856
|
-
const runtime = await preflightHost(exec, paths, resources);
|
|
8940
|
+
const runtime = await preflightHost(exec, paths, resources, void 0, log2);
|
|
8857
8941
|
const endpoints = [
|
|
8858
8942
|
...DEFAULT_ENDPOINTS,
|
|
8859
8943
|
{ host: "region1.v2.argotunnel.com", ports: [7844] },
|
|
8860
8944
|
{ host: "region2.v2.argotunnel.com", ports: [7844] }
|
|
8861
8945
|
];
|
|
8862
|
-
if (installUrl) endpoints.push({ host: parseInstallUrl(installUrl).hostname, ports: [443] });
|
|
8863
8946
|
const network = withHostSettings(await resolveNetwork(endpoints), {});
|
|
8864
8947
|
const template = renderVmTemplate(
|
|
8865
8948
|
runtime.arch,
|
|
@@ -8978,29 +9061,6 @@ async function beginEnrollment(ports, state) {
|
|
|
8978
9061
|
delete state.linkedDisplayName;
|
|
8979
9062
|
await saveState(ports.paths, state);
|
|
8980
9063
|
}
|
|
8981
|
-
async function enrollVm(ports, state, input) {
|
|
8982
|
-
const url = parseInstallUrl(input);
|
|
8983
|
-
if (!state.endpoints.some(
|
|
8984
|
-
(endpoint) => endpoint.host === url.hostname && endpoint.ports.includes(443)
|
|
8985
|
-
)) {
|
|
8986
|
-
throw new Error(
|
|
8987
|
-
`Approve ${url.hostname}:443 with vm allow while the VM is stopped, then retry.`
|
|
8988
|
-
);
|
|
8989
|
-
}
|
|
8990
|
-
await beginEnrollment(ports, state);
|
|
8991
|
-
ports.log("Preparing guest prerequisites and enrolling inside the VM.");
|
|
8992
|
-
await guestCommand(
|
|
8993
|
-
ports.exec,
|
|
8994
|
-
state,
|
|
8995
|
-
ports.paths,
|
|
8996
|
-
"set -e; export DEBIAN_FRONTEND=noninteractive; apt-get update -qq; apt-get install -y -qq curl unzip ca-certificates"
|
|
8997
|
-
);
|
|
8998
|
-
await guestCommand(ports.exec, state, ports.paths, GUEST_ENROLL, `${url.href}
|
|
8999
|
-
`);
|
|
9000
|
-
state.enrolled = true;
|
|
9001
|
-
await saveState(ports.paths, state);
|
|
9002
|
-
ports.log("Guest enrollment completed. Check Personal Compute for cluster health.");
|
|
9003
|
-
}
|
|
9004
9064
|
async function enrollVmBootstrap(ports, state, bootstrapScript) {
|
|
9005
9065
|
if (bootstrapScript.length === 0 || bootstrapScript.length > 1e6)
|
|
9006
9066
|
throw new Error("Pairing bootstrap payload is invalid.");
|
|
@@ -9556,7 +9616,7 @@ done
|
|
|
9556
9616
|
});
|
|
9557
9617
|
|
|
9558
9618
|
// src/vm/dev-artifact.ts
|
|
9559
|
-
import { createHash as
|
|
9619
|
+
import { createHash as createHash5 } from "crypto";
|
|
9560
9620
|
import { readFile as readFile7, stat } from "fs/promises";
|
|
9561
9621
|
async function readDevArtifact() {
|
|
9562
9622
|
const file = process.env.CONVEYOR_DEV_ARTIFACT;
|
|
@@ -9570,7 +9630,7 @@ async function readDevArtifact() {
|
|
|
9570
9630
|
if (!info.isFile() || info.size > 32 * 1024 * 1024)
|
|
9571
9631
|
throw new Error("Development artifact must be a file of at most 32 MiB.");
|
|
9572
9632
|
const bytes = await readFile7(file);
|
|
9573
|
-
if (bytes.length > 32 * 1024 * 1024 ||
|
|
9633
|
+
if (bytes.length > 32 * 1024 * 1024 || createHash5("sha256").update(bytes).digest("hex") !== digest)
|
|
9574
9634
|
throw new Error("Development artifact checksum mismatch.");
|
|
9575
9635
|
return { bytes, sha256: digest };
|
|
9576
9636
|
}
|
|
@@ -10665,14 +10725,6 @@ function commandPorts(exec, instance, log2, layout) {
|
|
|
10665
10725
|
otherAllocations: async () => (await readOtherAllocations(paths, layout)).map((other) => other.resources)
|
|
10666
10726
|
};
|
|
10667
10727
|
}
|
|
10668
|
-
async function readInstallInput() {
|
|
10669
|
-
let input = "";
|
|
10670
|
-
for await (const chunk of process.stdin) {
|
|
10671
|
-
input += String(chunk);
|
|
10672
|
-
if (input.length > 16384) throw new Error("Install URL exceeds the input limit.");
|
|
10673
|
-
}
|
|
10674
|
-
return input.trim();
|
|
10675
|
-
}
|
|
10676
10728
|
function resourceFlag(flags, key, fallback) {
|
|
10677
10729
|
if (flags[key] === void 0) return fallback;
|
|
10678
10730
|
if (typeof flags[key] !== "string" || !/^\d+$/.test(flags[key]))
|
|
@@ -10693,25 +10745,47 @@ function setupDefaults(others, host = hostCapacity()) {
|
|
|
10693
10745
|
`Your other VMs already reserve this computer's CPU or memory budget: ${reservations}. Stop one and lower it with vm configure --instance=<name> --cpus=<n> --memory=<GiB> or --profile=default, then retry.`
|
|
10694
10746
|
);
|
|
10695
10747
|
}
|
|
10748
|
+
const wanted = recommendedVmSize(host.memory / GIB2, host.cpus);
|
|
10696
10749
|
return {
|
|
10697
|
-
cpus: Math.min(
|
|
10698
|
-
memoryGiB: Math.min(
|
|
10750
|
+
cpus: Math.min(wanted.cpus, room.cpus),
|
|
10751
|
+
memoryGiB: Math.min(wanted.memoryGiB, room.memoryGiB),
|
|
10699
10752
|
diskGiB: 64
|
|
10700
10753
|
};
|
|
10701
10754
|
}
|
|
10702
|
-
|
|
10755
|
+
function setupResources(flags, defaults) {
|
|
10756
|
+
return {
|
|
10757
|
+
cpus: resourceFlag(flags, "cpus", defaults.cpus),
|
|
10758
|
+
memoryGiB: resourceFlag(flags, "memory", defaults.memoryGiB),
|
|
10759
|
+
diskGiB: resourceFlag(flags, "disk", defaults.diskGiB)
|
|
10760
|
+
};
|
|
10761
|
+
}
|
|
10762
|
+
function setupSizeNotice(resources, sizedToHost) {
|
|
10763
|
+
const fit = podsThatFit(resources);
|
|
10764
|
+
const pods = `${fit.medium} medium or ${fit.small} small pod${fit.small === 1 ? "" : "s"}`;
|
|
10765
|
+
const vm = `${resources.memoryGiB} GiB, ${resources.cpus}-CPU VM`;
|
|
10766
|
+
const lines2 = [
|
|
10767
|
+
sizedToHost ? `Creating a ${vm} sized to this computer. It fits ${pods} at a time. Pass --memory and --cpus to choose another size.` : `Creating a ${vm}. It fits ${pods} at a time.`
|
|
10768
|
+
];
|
|
10769
|
+
if (fit.small === 0) {
|
|
10770
|
+
const smallest = smallestVmFor("small");
|
|
10771
|
+
lines2.push(
|
|
10772
|
+
`No Conveyor pod fits in this VM, so builds skip it and use project compute. A small pod needs a VM with at least ${smallest.memoryGiB} GiB and ${smallest.cpus} CPUs.`
|
|
10773
|
+
);
|
|
10774
|
+
} else if (fit.medium === 0) {
|
|
10775
|
+
lines2.push(
|
|
10776
|
+
"Only small pods fit in this VM. Builds that need a medium pod use project compute."
|
|
10777
|
+
);
|
|
10778
|
+
}
|
|
10779
|
+
return lines2;
|
|
10780
|
+
}
|
|
10781
|
+
async function createInstance(ports, parsed) {
|
|
10703
10782
|
if (ports.paths.instance !== DEFAULT_INSTANCE) assertInstancePathFits(ports.paths);
|
|
10704
10783
|
return await withAdmissionLock(async (release) => {
|
|
10705
10784
|
const defaults = setupDefaults(await readOtherAllocations(ports.paths, ports.layout));
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
memoryGiB: resourceFlag(parsed.flags, "memory", defaults.memoryGiB),
|
|
10711
|
-
diskGiB: resourceFlag(parsed.flags, "disk", defaults.diskGiB)
|
|
10712
|
-
},
|
|
10713
|
-
url
|
|
10714
|
-
);
|
|
10785
|
+
const resources = setupResources(parsed.flags, defaults);
|
|
10786
|
+
const sizedToHost = parsed.flags.cpus === void 0 && parsed.flags.memory === void 0;
|
|
10787
|
+
for (const line of setupSizeNotice(resources, sizedToHost)) ports.log(line);
|
|
10788
|
+
return await createVm({ ...ports, reserved: release }, resources);
|
|
10715
10789
|
}, ports.layout.base);
|
|
10716
10790
|
}
|
|
10717
10791
|
async function configureWithAdmission(ports, state, parsed) {
|
|
@@ -10745,17 +10819,12 @@ function relinkNotice(state, relink) {
|
|
|
10745
10819
|
);
|
|
10746
10820
|
}
|
|
10747
10821
|
async function setupVm(ports, parsed, state) {
|
|
10748
|
-
const url = parsed.flags["install-url-stdin"] === true ? await readInstallInput() : void 0;
|
|
10749
|
-
if (url !== void 0) parseInstallUrl(url);
|
|
10750
10822
|
if (state && RESOURCE_FLAGS.some((key) => parsed.flags[key] !== void 0)) {
|
|
10751
10823
|
throw new Error("This VM already exists; setup resource flags only apply at creation.");
|
|
10752
10824
|
}
|
|
10753
|
-
|
|
10754
|
-
if (notice) ports.log(notice);
|
|
10755
|
-
if (!state) state = await createInstance(ports, parsed, url);
|
|
10825
|
+
if (!state) state = await createInstance(ports, parsed);
|
|
10756
10826
|
await startVm(ports, state);
|
|
10757
|
-
|
|
10758
|
-
else ports.log(setupNextStep(state, ports.paths.instance));
|
|
10827
|
+
ports.log(setupNextStep(state, ports.paths.instance));
|
|
10759
10828
|
}
|
|
10760
10829
|
function setupNextStep(state, instance) {
|
|
10761
10830
|
const link = instance === DEFAULT_INSTANCE ? "conveyor-k3 vm link" : `conveyor-k3 vm link --instance=${instance}`;
|
|
@@ -10859,6 +10928,7 @@ var RESOURCE_FLAGS;
|
|
|
10859
10928
|
var init_command_setup = __esm({
|
|
10860
10929
|
"src/vm/command-setup.ts"() {
|
|
10861
10930
|
"use strict";
|
|
10931
|
+
init_dist();
|
|
10862
10932
|
init_config();
|
|
10863
10933
|
init_dev_artifact();
|
|
10864
10934
|
init_device_link();
|
|
@@ -10932,7 +11002,7 @@ var init_service_unit = __esm({
|
|
|
10932
11002
|
});
|
|
10933
11003
|
|
|
10934
11004
|
// src/vm/service-control.ts
|
|
10935
|
-
import { constants as constants2, readFileSync as
|
|
11005
|
+
import { constants as constants2, readFileSync as readFileSync5 } from "fs";
|
|
10936
11006
|
import { access as access5, lstat as lstat3, mkdir as mkdir6, readFile as readFile8, readdir as readdir2, rename as rename3, rm as rm7, writeFile as writeFile6 } from "fs/promises";
|
|
10937
11007
|
import { tmpdir as tmpdir2, userInfo as userInfo3 } from "os";
|
|
10938
11008
|
import { dirname as dirname3, join as join12 } from "path";
|
|
@@ -10966,7 +11036,7 @@ function realServiceHost(exec, log2) {
|
|
|
10966
11036
|
function cliVersion() {
|
|
10967
11037
|
try {
|
|
10968
11038
|
const url = new URL("../package.json", import.meta.url);
|
|
10969
|
-
return JSON.parse(
|
|
11039
|
+
return JSON.parse(readFileSync5(url, "utf8")).version ?? "<version>";
|
|
10970
11040
|
} catch {
|
|
10971
11041
|
return "<version>";
|
|
10972
11042
|
}
|
|
@@ -11616,7 +11686,7 @@ function validateFlags(parsed) {
|
|
|
11616
11686
|
const action = actionOf(parsed);
|
|
11617
11687
|
const allowed = {
|
|
11618
11688
|
...BAKE_VERBS,
|
|
11619
|
-
setup: ["
|
|
11689
|
+
setup: ["cpus", "memory", "disk"],
|
|
11620
11690
|
start: [],
|
|
11621
11691
|
configure: [...RESOURCE_FLAGS2, ...CAPACITY_FLAGS],
|
|
11622
11692
|
stop: ["force"],
|
|
@@ -11640,7 +11710,7 @@ function validateFlags(parsed) {
|
|
|
11640
11710
|
for (const flag of Object.keys(parsed.flags)) {
|
|
11641
11711
|
if (!accepted.includes(flag)) throw new Error(`Unknown flag for vm ${action}: --${flag}`);
|
|
11642
11712
|
}
|
|
11643
|
-
const booleanFlags = /* @__PURE__ */ new Set(["
|
|
11713
|
+
const booleanFlags = /* @__PURE__ */ new Set(["force", "json", "purge", "yes", "relink"]);
|
|
11644
11714
|
for (const [flag, value] of Object.entries(parsed.flags)) {
|
|
11645
11715
|
if (booleanFlags.has(flag) && value !== true) throw new Error(`Use --${flag} without a value.`);
|
|
11646
11716
|
}
|
|
@@ -11761,7 +11831,8 @@ var init_command = __esm({
|
|
|
11761
11831
|
Usage: conveyor-k3 vm <setup|start|stop|status|list|link|internet|logout|pods|pod-logs|disk-usage|configure|update|allow|deny|refresh|uninstall|image import|runners grant>
|
|
11762
11832
|
Every command but list acts on one VM: --instance=<name> selects a named VM,
|
|
11763
11833
|
and omitting it selects this user's default VM.
|
|
11764
|
-
setup [--
|
|
11834
|
+
setup [--cpus=<n>] [--memory=<GiB>] [--disk=64] Without --cpus and --memory, sizes the VM
|
|
11835
|
+
to this computer: the largest the budget allows, up to 32 GiB and 8 CPUs.
|
|
11765
11836
|
configure --cpus=4 --memory=8 [--disk=64] Changes allocation while stopped.
|
|
11766
11837
|
configure --profile=default|server [--relay-slots=96] [--relay-max-gib=8]
|
|
11767
11838
|
[--disk-mbps=1000] [--disk-iops=20000] Changes network and disk capacity while stopped.
|
|
@@ -11795,6 +11866,7 @@ Run without sudo. Requires Lima 2.2+ and QEMU with HVF (macOS) or KVM (Linux).
|
|
|
11795
11866
|
var command_exports2 = {};
|
|
11796
11867
|
__export(command_exports2, {
|
|
11797
11868
|
HOST_HELP: () => HOST_HELP,
|
|
11869
|
+
hostNextStep: () => hostNextStep,
|
|
11798
11870
|
runHostCommand: () => runHostCommand
|
|
11799
11871
|
});
|
|
11800
11872
|
import { hostname } from "os";
|
|
@@ -11848,6 +11920,11 @@ async function prepareHostLink(parsed, ports, log2) {
|
|
|
11848
11920
|
}
|
|
11849
11921
|
return { api, name: name2 };
|
|
11850
11922
|
}
|
|
11923
|
+
function hostNextStep(state) {
|
|
11924
|
+
if (state.k3sInstalledByConveyor === true)
|
|
11925
|
+
return "Next: in Conveyor open this instance, set Keep for this machine (CPU and memory), max concurrent and projects, and Save. Conveyor restarts k3s once to apply them, while no build runs.";
|
|
11926
|
+
return "Next: in Conveyor open this instance, set Keep for this machine (CPU and memory), max concurrent and projects, Save, then run `sudo systemctl restart k3s` once while no build runs. Conveyor never restarts a k3s it did not install.";
|
|
11927
|
+
}
|
|
11851
11928
|
async function completeHostLink(api, pairing, log2, ports) {
|
|
11852
11929
|
const poll = () => pollPairing(api, pairing.deviceCode);
|
|
11853
11930
|
const retry = () => log2("Securing connection\u2026 retrying pairing status.");
|
|
@@ -11880,6 +11957,7 @@ async function completeHostLink(api, pairing, log2, ports) {
|
|
|
11880
11957
|
log2(
|
|
11881
11958
|
`Linked to ${completed.accountLabel ?? "Conveyor"}${completed.displayName ? ` as ${completed.displayName}` : ""}.`
|
|
11882
11959
|
);
|
|
11960
|
+
log2(hostNextStep(await ports.readState()));
|
|
11883
11961
|
}
|
|
11884
11962
|
async function hostLink(parsed, log2, ports) {
|
|
11885
11963
|
const { api, name: name2 } = await prepareHostLink(parsed, ports, log2);
|
|
@@ -11944,8 +12022,10 @@ Usage: conveyor-k3 host <setup|link|status|uninstall>
|
|
|
11944
12022
|
uninstall [--purge-k3s --yes] Remove Conveyor's units, state, and device key. k3s,
|
|
11945
12023
|
your config, and your workloads stay unless you purge.
|
|
11946
12024
|
Run with sudo on a dedicated Linux server. Builds run on this server's own k3s with
|
|
11947
|
-
no VM boundary; Pod Security and an egress policy stand in for one.
|
|
11948
|
-
|
|
12025
|
+
no VM boundary; Pod Security and an egress policy stand in for one. After link, on a
|
|
12026
|
+
k3s you installed yourself: set Keep for this machine in Conveyor and Save, then run
|
|
12027
|
+
sudo systemctl restart k3s once. Conveyor never restarts a k3s it did not install.
|
|
12028
|
+
See the guide's "Link a dedicated server (host)" section.
|
|
11949
12029
|
`;
|
|
11950
12030
|
ALLOWED_FLAGS = /* @__PURE__ */ new Map([
|
|
11951
12031
|
["setup", []],
|
|
@@ -12026,7 +12106,7 @@ async function writeWorkloads(data) {
|
|
|
12026
12106
|
|
|
12027
12107
|
// src/cli.ts
|
|
12028
12108
|
init_exec();
|
|
12029
|
-
import { readFileSync as
|
|
12109
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
12030
12110
|
|
|
12031
12111
|
// src/lib/fs-port.ts
|
|
12032
12112
|
import { chmod, mkdir as mkdir2, readFile, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -12040,7 +12120,7 @@ var nodeFileSystem = {
|
|
|
12040
12120
|
},
|
|
12041
12121
|
async writeFile(path, content, mode) {
|
|
12042
12122
|
await mkdir2(path.slice(0, path.lastIndexOf("/")), { recursive: true });
|
|
12043
|
-
await writeFile2(path, content, "utf8");
|
|
12123
|
+
await writeFile2(path, content, { encoding: "utf8", ...mode === void 0 ? {} : { mode } });
|
|
12044
12124
|
if (mode !== void 0) await chmod(path, mode);
|
|
12045
12125
|
},
|
|
12046
12126
|
async removeFile(path) {
|
|
@@ -12091,11 +12171,235 @@ init_dist();
|
|
|
12091
12171
|
// src/lib/local-registry.ts
|
|
12092
12172
|
init_dist();
|
|
12093
12173
|
init_paths();
|
|
12174
|
+
import { createHash as createHash2 } from "crypto";
|
|
12175
|
+
|
|
12176
|
+
// src/lib/registry-credentials.ts
|
|
12177
|
+
import { createHash, randomBytes } from "crypto";
|
|
12178
|
+
|
|
12179
|
+
// src/lib/bcrypt.ts
|
|
12180
|
+
var P_WORDS = 18;
|
|
12181
|
+
var S_WORDS = 1024;
|
|
12182
|
+
var MAGIC = "OrpheanBeholderScryDoubt";
|
|
12183
|
+
var ALPHABET = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
12184
|
+
var MAX_KEY_BYTES = 72;
|
|
12185
|
+
var BCRYPT_SALT_BYTES = 16;
|
|
12186
|
+
function scaledAtanInverse(x, one) {
|
|
12187
|
+
const xSquared = x * x;
|
|
12188
|
+
let power = one / x;
|
|
12189
|
+
let sum = 0n;
|
|
12190
|
+
for (let n = 0n; power !== 0n; n++) {
|
|
12191
|
+
const term = power / (2n * n + 1n);
|
|
12192
|
+
sum = n % 2n === 0n ? sum + term : sum - term;
|
|
12193
|
+
power /= xSquared;
|
|
12194
|
+
}
|
|
12195
|
+
return sum;
|
|
12196
|
+
}
|
|
12197
|
+
function piFractionWords(count) {
|
|
12198
|
+
const guard = 64n;
|
|
12199
|
+
const bits = BigInt(count * 32) + guard;
|
|
12200
|
+
const one = 1n << bits;
|
|
12201
|
+
const pi = 16n * scaledAtanInverse(5n, one) - 4n * scaledAtanInverse(239n, one);
|
|
12202
|
+
const fraction = pi - 3n * one >> guard;
|
|
12203
|
+
const words = new Uint32Array(count);
|
|
12204
|
+
for (let index = 0; index < count; index++) {
|
|
12205
|
+
words[index] = Number(fraction >> BigInt(32 * (count - 1 - index)) & 0xffffffffn);
|
|
12206
|
+
}
|
|
12207
|
+
return words;
|
|
12208
|
+
}
|
|
12209
|
+
var initialState = null;
|
|
12210
|
+
function blowfishInitialState() {
|
|
12211
|
+
if (!initialState) {
|
|
12212
|
+
const words = piFractionWords(P_WORDS + S_WORDS);
|
|
12213
|
+
initialState = { p: words.slice(0, P_WORDS), s: words.slice(P_WORDS) };
|
|
12214
|
+
}
|
|
12215
|
+
return initialState;
|
|
12216
|
+
}
|
|
12217
|
+
function word(array, index) {
|
|
12218
|
+
return array[index] ?? 0;
|
|
12219
|
+
}
|
|
12220
|
+
function round(s, x) {
|
|
12221
|
+
const mixed = word(s, x >>> 24) + word(s, 256 + (x >>> 16 & 255)) ^ word(s, 512 + (x >>> 8 & 255));
|
|
12222
|
+
return mixed + word(s, 768 + (x & 255)) | 0;
|
|
12223
|
+
}
|
|
12224
|
+
function encipher(state, data, offset) {
|
|
12225
|
+
const { p, s } = state;
|
|
12226
|
+
let left = word(data, offset) ^ word(p, 0);
|
|
12227
|
+
let right = word(data, offset + 1);
|
|
12228
|
+
for (let index = 1; index <= 16; index += 2) {
|
|
12229
|
+
right ^= round(s, left) ^ word(p, index);
|
|
12230
|
+
left ^= round(s, right) ^ word(p, index + 1);
|
|
12231
|
+
}
|
|
12232
|
+
data[offset] = right ^ word(p, 17);
|
|
12233
|
+
data[offset + 1] = left;
|
|
12234
|
+
}
|
|
12235
|
+
function wordStream(bytes) {
|
|
12236
|
+
let position = 0;
|
|
12237
|
+
return () => {
|
|
12238
|
+
let value = 0;
|
|
12239
|
+
for (let index = 0; index < 4; index++) {
|
|
12240
|
+
value = value << 8 | (bytes[position] ?? 0);
|
|
12241
|
+
position = (position + 1) % bytes.length;
|
|
12242
|
+
}
|
|
12243
|
+
return value >>> 0;
|
|
12244
|
+
};
|
|
12245
|
+
}
|
|
12246
|
+
function expandKey(state, key, salt) {
|
|
12247
|
+
const nextKeyWord = wordStream(key);
|
|
12248
|
+
for (let index = 0; index < P_WORDS; index++) {
|
|
12249
|
+
state.p[index] = word(state.p, index) ^ nextKeyWord();
|
|
12250
|
+
}
|
|
12251
|
+
const nextSaltWord = salt ? wordStream(salt) : () => 0;
|
|
12252
|
+
const block = new Uint32Array(2);
|
|
12253
|
+
const fill = (target2, length) => {
|
|
12254
|
+
for (let index = 0; index < length; index += 2) {
|
|
12255
|
+
block[0] = word(block, 0) ^ nextSaltWord();
|
|
12256
|
+
block[1] = word(block, 1) ^ nextSaltWord();
|
|
12257
|
+
encipher(state, block, 0);
|
|
12258
|
+
target2[index] = word(block, 0);
|
|
12259
|
+
target2[index + 1] = word(block, 1);
|
|
12260
|
+
}
|
|
12261
|
+
};
|
|
12262
|
+
fill(state.p, P_WORDS);
|
|
12263
|
+
fill(state.s, S_WORDS);
|
|
12264
|
+
}
|
|
12265
|
+
function encodeBase64(bytes, length) {
|
|
12266
|
+
let out = "";
|
|
12267
|
+
let offset = 0;
|
|
12268
|
+
while (offset < length) {
|
|
12269
|
+
let first = bytes[offset++] ?? 0;
|
|
12270
|
+
out += ALPHABET[first >> 2];
|
|
12271
|
+
first = (first & 3) << 4;
|
|
12272
|
+
if (offset >= length) {
|
|
12273
|
+
out += ALPHABET[first];
|
|
12274
|
+
break;
|
|
12275
|
+
}
|
|
12276
|
+
let second = bytes[offset++] ?? 0;
|
|
12277
|
+
out += ALPHABET[first | second >> 4];
|
|
12278
|
+
first = (second & 15) << 2;
|
|
12279
|
+
if (offset >= length) {
|
|
12280
|
+
out += ALPHABET[first];
|
|
12281
|
+
break;
|
|
12282
|
+
}
|
|
12283
|
+
second = bytes[offset++] ?? 0;
|
|
12284
|
+
out += ALPHABET[first | second >> 6];
|
|
12285
|
+
out += ALPHABET[second & 63];
|
|
12286
|
+
}
|
|
12287
|
+
return out;
|
|
12288
|
+
}
|
|
12289
|
+
function bcryptHash(password, cost, salt) {
|
|
12290
|
+
if (!Number.isInteger(cost) || cost < 4 || cost > 31) {
|
|
12291
|
+
throw new Error(`bcrypt cost must be an integer from 4 to 31, not ${cost}`);
|
|
12292
|
+
}
|
|
12293
|
+
if (salt.length !== BCRYPT_SALT_BYTES) {
|
|
12294
|
+
throw new Error(`bcrypt needs a ${BCRYPT_SALT_BYTES}-byte salt`);
|
|
12295
|
+
}
|
|
12296
|
+
const passwordBytes = new TextEncoder().encode(password).slice(0, MAX_KEY_BYTES);
|
|
12297
|
+
const key = new Uint8Array(passwordBytes.length + 1);
|
|
12298
|
+
key.set(passwordBytes);
|
|
12299
|
+
const base = blowfishInitialState();
|
|
12300
|
+
const state = { p: base.p.slice(), s: base.s.slice() };
|
|
12301
|
+
expandKey(state, key, salt);
|
|
12302
|
+
for (let rounds = 2 ** cost; rounds > 0; rounds--) {
|
|
12303
|
+
expandKey(state, key, null);
|
|
12304
|
+
expandKey(state, salt, null);
|
|
12305
|
+
}
|
|
12306
|
+
const magic = new TextEncoder().encode(MAGIC);
|
|
12307
|
+
const nextMagicWord = wordStream(magic);
|
|
12308
|
+
const block = Uint32Array.from({ length: 6 }, () => nextMagicWord());
|
|
12309
|
+
for (let pass = 0; pass < 64; pass++) {
|
|
12310
|
+
for (let offset = 0; offset < 6; offset += 2) encipher(state, block, offset);
|
|
12311
|
+
}
|
|
12312
|
+
const digest = new Uint8Array(24);
|
|
12313
|
+
block.forEach((value, index) => {
|
|
12314
|
+
digest[index * 4] = value >>> 24;
|
|
12315
|
+
digest[index * 4 + 1] = value >>> 16 & 255;
|
|
12316
|
+
digest[index * 4 + 2] = value >>> 8 & 255;
|
|
12317
|
+
digest[index * 4 + 3] = value & 255;
|
|
12318
|
+
});
|
|
12319
|
+
const costText = String(cost).padStart(2, "0");
|
|
12320
|
+
return `$2y$${costText}$${encodeBase64(salt, BCRYPT_SALT_BYTES)}${encodeBase64(digest, 23)}`;
|
|
12321
|
+
}
|
|
12322
|
+
|
|
12323
|
+
// src/lib/registry-credentials.ts
|
|
12324
|
+
init_paths();
|
|
12325
|
+
var LOCAL_REGISTRY_USERNAME = "conveyor";
|
|
12326
|
+
var BCRYPT_COST = 5;
|
|
12327
|
+
var USERNAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
12328
|
+
var PASSWORD = /^[A-Za-z0-9_-]{32,128}$/;
|
|
12329
|
+
function generateRegistryCredentials(random = randomBytes) {
|
|
12330
|
+
const username = LOCAL_REGISTRY_USERNAME;
|
|
12331
|
+
const password = Buffer.from(random(32)).toString("base64url");
|
|
12332
|
+
const hash = bcryptHash(password, BCRYPT_COST, random(BCRYPT_SALT_BYTES));
|
|
12333
|
+
return { username, password, htpasswd: `${username}:${hash}` };
|
|
12334
|
+
}
|
|
12335
|
+
function parseRegistryCredentials(raw) {
|
|
12336
|
+
if (!raw) return null;
|
|
12337
|
+
try {
|
|
12338
|
+
const value = JSON.parse(raw);
|
|
12339
|
+
const { username, password, htpasswd } = value ?? {};
|
|
12340
|
+
if (typeof username !== "string" || !USERNAME.test(username)) return null;
|
|
12341
|
+
if (typeof password !== "string" || !PASSWORD.test(password)) return null;
|
|
12342
|
+
if (typeof htpasswd !== "string" || !htpasswd.startsWith(`${username}:$2`)) return null;
|
|
12343
|
+
if (htpasswd.includes("\n")) return null;
|
|
12344
|
+
return { username, password, htpasswd };
|
|
12345
|
+
} catch {
|
|
12346
|
+
return null;
|
|
12347
|
+
}
|
|
12348
|
+
}
|
|
12349
|
+
async function loadOrCreateRegistryCredentials(fs2, random) {
|
|
12350
|
+
const stored = parseRegistryCredentials(await fs2.readFile(LOCAL_REGISTRY_CREDENTIALS_FILE));
|
|
12351
|
+
if (stored) return stored;
|
|
12352
|
+
const created = generateRegistryCredentials(random);
|
|
12353
|
+
await fs2.writeFile(LOCAL_REGISTRY_CREDENTIALS_FILE, `${JSON.stringify(created)}
|
|
12354
|
+
`, 384);
|
|
12355
|
+
return created;
|
|
12356
|
+
}
|
|
12357
|
+
function credentialFingerprint(credentials) {
|
|
12358
|
+
return createHash("sha256").update(`${credentials.username}:${credentials.password}`).digest("hex").slice(0, 16);
|
|
12359
|
+
}
|
|
12360
|
+
function basicAuthorization(credentials) {
|
|
12361
|
+
return `Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64")}`;
|
|
12362
|
+
}
|
|
12363
|
+
function registryDockerConfig(credentials, host) {
|
|
12364
|
+
const auth = Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64");
|
|
12365
|
+
return JSON.stringify({ auths: { [host]: { auth } } });
|
|
12366
|
+
}
|
|
12367
|
+
function parseRegistryAuthState(raw) {
|
|
12368
|
+
if (!raw) return null;
|
|
12369
|
+
try {
|
|
12370
|
+
const value = JSON.parse(raw);
|
|
12371
|
+
if (typeof value?.credential !== "string") return null;
|
|
12372
|
+
const writtenAt = typeof value.writtenAt === "string" && !Number.isNaN(Date.parse(value.writtenAt)) ? value.writtenAt : null;
|
|
12373
|
+
return { credential: value.credential, writtenAt, enforced: value.enforced === true };
|
|
12374
|
+
} catch {
|
|
12375
|
+
return null;
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
async function registryAuthEnforced(readFile9) {
|
|
12379
|
+
return parseRegistryAuthState(await readFile9(LOCAL_REGISTRY_AUTH_STATE_FILE))?.enforced === true;
|
|
12380
|
+
}
|
|
12381
|
+
function withRegistryCredentials(fetchImpl, readFile9) {
|
|
12382
|
+
let credentials = null;
|
|
12383
|
+
return async (url, init) => {
|
|
12384
|
+
credentials ??= readFile9(LOCAL_REGISTRY_CREDENTIALS_FILE).then(parseRegistryCredentials);
|
|
12385
|
+
const stored = await credentials;
|
|
12386
|
+
if (!stored) return fetchImpl(url, init);
|
|
12387
|
+
const headers = new Headers(init?.headers);
|
|
12388
|
+
headers.set("Authorization", basicAuthorization(stored));
|
|
12389
|
+
return fetchImpl(url, { ...init, headers });
|
|
12390
|
+
};
|
|
12391
|
+
}
|
|
12392
|
+
|
|
12393
|
+
// src/lib/local-registry.ts
|
|
12094
12394
|
var LOCAL_REGISTRY_IMAGE = "docker.io/library/registry:3.1.2@sha256:c87f33837722a100572e95d7dc4bf539fc42cf68202b13c3bc03c0ff54c3a649";
|
|
12095
12395
|
var LOCAL_REGISTRY_CONFIG = "/etc/distribution/config.yml";
|
|
12096
12396
|
var LOCAL_REGISTRY_GC_JOB = "conveyor-registry-gc";
|
|
12397
|
+
var LOCAL_REGISTRY_HTPASSWD_SECRET = "conveyor-registry-htpasswd";
|
|
12398
|
+
var HTPASSWD_KEY = "htpasswd";
|
|
12399
|
+
var AUTH_MOUNT = "/etc/distribution/auth";
|
|
12097
12400
|
var LOCAL_REGISTRY_SELECTOR = { "app.kubernetes.io/name": PERSONAL_LOCAL_REGISTRY_NAME };
|
|
12098
12401
|
var MANAGED_BY = { "app.kubernetes.io/managed-by": "conveyor-k3" };
|
|
12402
|
+
var AUTH_CHECKSUM_ANNOTATION = "conveyor.rallycry.com/registry-auth";
|
|
12099
12403
|
var DATA_MOUNT = "/var/lib/registry";
|
|
12100
12404
|
var REGISTRY_ENV = [
|
|
12101
12405
|
{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" },
|
|
@@ -12110,6 +12414,23 @@ var VOLUME_MOUNTS = [
|
|
|
12110
12414
|
{ name: "data", mountPath: DATA_MOUNT },
|
|
12111
12415
|
{ name: "tmp", mountPath: "/tmp" }
|
|
12112
12416
|
];
|
|
12417
|
+
var AUTH_ENV = [
|
|
12418
|
+
{ name: "REGISTRY_AUTH", value: "htpasswd" },
|
|
12419
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_REALM", value: "conveyor" },
|
|
12420
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_PATH", value: `${AUTH_MOUNT}/${HTPASSWD_KEY}` }
|
|
12421
|
+
];
|
|
12422
|
+
var AUTH_VOLUME = {
|
|
12423
|
+
name: "auth",
|
|
12424
|
+
secret: {
|
|
12425
|
+
secretName: LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
12426
|
+
items: [{ key: HTPASSWD_KEY, path: HTPASSWD_KEY }],
|
|
12427
|
+
defaultMode: 256
|
|
12428
|
+
}
|
|
12429
|
+
};
|
|
12430
|
+
var AUTH_VOLUME_MOUNT = { name: "auth", mountPath: AUTH_MOUNT, readOnly: true };
|
|
12431
|
+
function htpasswdChecksum(htpasswd) {
|
|
12432
|
+
return createHash2("sha256").update(htpasswd).digest("hex").slice(0, 16);
|
|
12433
|
+
}
|
|
12113
12434
|
var VOLUMES = [
|
|
12114
12435
|
{ name: "data", hostPath: { path: LOCAL_REGISTRY_DATA_DIR, type: "Directory" } },
|
|
12115
12436
|
{ name: "tmp", emptyDir: { sizeLimit: "64Mi" } }
|
|
@@ -12146,7 +12467,34 @@ function registryNetworkPolicy(metadata) {
|
|
|
12146
12467
|
}
|
|
12147
12468
|
};
|
|
12148
12469
|
}
|
|
12149
|
-
function
|
|
12470
|
+
function renderLocalRegistrySecrets(credentials) {
|
|
12471
|
+
const encode = (value) => Buffer.from(value).toString("base64");
|
|
12472
|
+
const secret = (namespace, name2, key, value) => ({
|
|
12473
|
+
apiVersion: "v1",
|
|
12474
|
+
kind: "Secret",
|
|
12475
|
+
metadata: { name: name2, namespace, labels: { ...MANAGED_BY } },
|
|
12476
|
+
type: "Opaque",
|
|
12477
|
+
data: { [key]: encode(value) }
|
|
12478
|
+
});
|
|
12479
|
+
const items = [
|
|
12480
|
+
secret(
|
|
12481
|
+
PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
12482
|
+
LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
12483
|
+
HTPASSWD_KEY,
|
|
12484
|
+
`${credentials.htpasswd}
|
|
12485
|
+
`
|
|
12486
|
+
),
|
|
12487
|
+
secret(
|
|
12488
|
+
PERSONAL_BAKE_NAMESPACE,
|
|
12489
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET,
|
|
12490
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY,
|
|
12491
|
+
registryDockerConfig(credentials, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
12492
|
+
)
|
|
12493
|
+
];
|
|
12494
|
+
return `${JSON.stringify({ apiVersion: "v1", kind: "List", items }, null, 2)}
|
|
12495
|
+
`;
|
|
12496
|
+
}
|
|
12497
|
+
function renderLocalRegistryManifests(auth = null) {
|
|
12150
12498
|
const metadata = {
|
|
12151
12499
|
name: PERSONAL_LOCAL_REGISTRY_NAME,
|
|
12152
12500
|
namespace: PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
@@ -12162,15 +12510,19 @@ function renderLocalRegistryManifests() {
|
|
|
12162
12510
|
strategy: { type: "Recreate" },
|
|
12163
12511
|
selector: { matchLabels: LOCAL_REGISTRY_SELECTOR },
|
|
12164
12512
|
template: {
|
|
12165
|
-
metadata: {
|
|
12513
|
+
metadata: {
|
|
12514
|
+
labels: metadata.labels,
|
|
12515
|
+
...auth ? { annotations: { [AUTH_CHECKSUM_ANNOTATION]: htpasswdChecksum(auth.htpasswd) } } : {}
|
|
12516
|
+
},
|
|
12166
12517
|
spec: {
|
|
12167
12518
|
...POD_BASE,
|
|
12519
|
+
...auth ? { volumes: [...VOLUMES, AUTH_VOLUME] } : {},
|
|
12168
12520
|
containers: [
|
|
12169
12521
|
{
|
|
12170
12522
|
name: "registry",
|
|
12171
12523
|
image: LOCAL_REGISTRY_IMAGE,
|
|
12172
12524
|
imagePullPolicy: "IfNotPresent",
|
|
12173
|
-
env: REGISTRY_ENV,
|
|
12525
|
+
env: auth ? [...REGISTRY_ENV, ...AUTH_ENV] : REGISTRY_ENV,
|
|
12174
12526
|
ports: [
|
|
12175
12527
|
{
|
|
12176
12528
|
name: "registry",
|
|
@@ -12182,8 +12534,10 @@ function renderLocalRegistryManifests() {
|
|
|
12182
12534
|
protocol: "TCP"
|
|
12183
12535
|
}
|
|
12184
12536
|
],
|
|
12537
|
+
// A TCP check: once the credential is enforced, `/v2/` answers
|
|
12538
|
+
// 401 to a probe that does not carry it.
|
|
12185
12539
|
readinessProbe: {
|
|
12186
|
-
|
|
12540
|
+
tcpSocket: { port: PERSONAL_LOCAL_REGISTRY_PORT },
|
|
12187
12541
|
periodSeconds: 10
|
|
12188
12542
|
},
|
|
12189
12543
|
resources: {
|
|
@@ -12191,7 +12545,7 @@ function renderLocalRegistryManifests() {
|
|
|
12191
12545
|
limits: { memory: "512Mi" }
|
|
12192
12546
|
},
|
|
12193
12547
|
securityContext: CONTAINER_SECURITY,
|
|
12194
|
-
volumeMounts: VOLUME_MOUNTS
|
|
12548
|
+
volumeMounts: auth ? [...VOLUME_MOUNTS, AUTH_VOLUME_MOUNT] : VOLUME_MOUNTS
|
|
12195
12549
|
}
|
|
12196
12550
|
]
|
|
12197
12551
|
}
|
|
@@ -12282,11 +12636,18 @@ async function registryAnswers(fetchImpl = fetch) {
|
|
|
12282
12636
|
}
|
|
12283
12637
|
async function localRegistryReady(ports) {
|
|
12284
12638
|
const [answers, mirrored] = await Promise.all([
|
|
12285
|
-
registryAnswers(ports.fetch),
|
|
12639
|
+
registryAnswers(withRegistryCredentials(ports.fetch ?? fetch, ports.readFile)),
|
|
12286
12640
|
mirrorLoaded(ports.readFile)
|
|
12287
12641
|
]);
|
|
12288
12642
|
return answers && mirrored;
|
|
12289
12643
|
}
|
|
12644
|
+
async function localRegistryStatus(ports) {
|
|
12645
|
+
const [ready, authEnforced] = await Promise.all([
|
|
12646
|
+
localRegistryReady(ports),
|
|
12647
|
+
registryAuthEnforced(ports.readFile)
|
|
12648
|
+
]);
|
|
12649
|
+
return { ready, authEnforced };
|
|
12650
|
+
}
|
|
12290
12651
|
|
|
12291
12652
|
// src/daemon/registry-gc.ts
|
|
12292
12653
|
init_paths();
|
|
@@ -12683,7 +13044,10 @@ async function collectRegistryGarbage(ports, options) {
|
|
|
12683
13044
|
blobsCollected: false
|
|
12684
13045
|
});
|
|
12685
13046
|
const now = ports.now ?? Date.now;
|
|
12686
|
-
const fetchImpl =
|
|
13047
|
+
const fetchImpl = withRegistryCredentials(
|
|
13048
|
+
ports.fetch ?? fetch,
|
|
13049
|
+
ports.readFile ?? ((path) => nodeFileSystem.readFile(path))
|
|
13050
|
+
);
|
|
12687
13051
|
const retain = await readRetainList(ports);
|
|
12688
13052
|
if (typeof retain === "string") return skip(retain);
|
|
12689
13053
|
const blocked = await registryBlocker(ports, now());
|
|
@@ -12750,6 +13114,16 @@ function upsertRegistryAuth(file, host, auth) {
|
|
|
12750
13114
|
next.configs = configs;
|
|
12751
13115
|
return next;
|
|
12752
13116
|
}
|
|
13117
|
+
function removeRegistryAuth(file, host) {
|
|
13118
|
+
const existing = file.configs?.[host];
|
|
13119
|
+
if (!existing?.auth) return file;
|
|
13120
|
+
const { auth: _auth, ...rest } = existing;
|
|
13121
|
+
const { [host]: _removed, ...others } = file.configs ?? {};
|
|
13122
|
+
const configs = Object.keys(rest).length > 0 ? { ...others, [host]: rest } : others;
|
|
13123
|
+
const next = { ...file, configs };
|
|
13124
|
+
if (Object.keys(configs).length === 0) delete next.configs;
|
|
13125
|
+
return next;
|
|
13126
|
+
}
|
|
12753
13127
|
function mirrorEndpoints(file, host) {
|
|
12754
13128
|
const entry = file.mirrors?.[host];
|
|
12755
13129
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
@@ -12966,6 +13340,126 @@ var GRANTED_COMMAND_NAMES = LOCAL_BAKE_ROOT_COMMANDS.map((command) => command.na
|
|
|
12966
13340
|
// src/commands/uninstall.ts
|
|
12967
13341
|
init_dist();
|
|
12968
13342
|
|
|
13343
|
+
// src/daemon/pod-cpu-quota.ts
|
|
13344
|
+
init_safe_arg();
|
|
13345
|
+
var POD_SLICE = "kubepods.slice";
|
|
13346
|
+
function cpuMillicores(quantity) {
|
|
13347
|
+
const checked = assertQuantity(quantity, "reserveCpu");
|
|
13348
|
+
const match = /^(\d+(?:\.\d+)?)(m?)$/.exec(checked);
|
|
13349
|
+
if (!match?.[1])
|
|
13350
|
+
throw new Error(`Invalid reserveCpu: ${JSON.stringify(checked)} is not a CPU amount`);
|
|
13351
|
+
const amount = Number.parseFloat(match[1]);
|
|
13352
|
+
return Math.round(match[2] === "m" ? amount : amount * 1e3);
|
|
13353
|
+
}
|
|
13354
|
+
function podCpuQuotaPercent(cores, reservedMillicores) {
|
|
13355
|
+
const left = cores * 1e3 - reservedMillicores;
|
|
13356
|
+
if (left < 10) return null;
|
|
13357
|
+
return Math.floor(left / 10);
|
|
13358
|
+
}
|
|
13359
|
+
function setQuota(exec, value) {
|
|
13360
|
+
return exec("systemctl", ["set-property", POD_SLICE, `CPUQuota=${value}`]);
|
|
13361
|
+
}
|
|
13362
|
+
async function liftPodCpuQuota(exec) {
|
|
13363
|
+
const shown = await exec("systemctl", ["show", POD_SLICE, "-p", "CPUQuotaPerSecUSec", "--value"]);
|
|
13364
|
+
if (shown.exitCode !== 0 || ["", "infinity"].includes(shown.stdout.trim())) return null;
|
|
13365
|
+
const cleared = await setQuota(exec, "");
|
|
13366
|
+
return cleared.exitCode === 0 ? null : cleared.stderr.trim().slice(0, 200);
|
|
13367
|
+
}
|
|
13368
|
+
async function reconcilePodCpuQuota(exec, desired, warnings) {
|
|
13369
|
+
const reserve = desired.kubelet.systemReserved?.cpu;
|
|
13370
|
+
if (!reserve) {
|
|
13371
|
+
const failure = await liftPodCpuQuota(exec);
|
|
13372
|
+
if (failure) warnings.push(`Could not lift the CPU cap on ${POD_SLICE}: ${failure}`);
|
|
13373
|
+
return void 0;
|
|
13374
|
+
}
|
|
13375
|
+
const reserved = cpuMillicores(reserve);
|
|
13376
|
+
const state = await exec("systemctl", ["show", POD_SLICE, "-p", "ActiveState", "--value"]);
|
|
13377
|
+
if (state.exitCode !== 0 || state.stdout.trim() !== "active") {
|
|
13378
|
+
warnings.push(
|
|
13379
|
+
`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.`
|
|
13380
|
+
);
|
|
13381
|
+
return false;
|
|
13382
|
+
}
|
|
13383
|
+
const nproc = await exec("nproc", []);
|
|
13384
|
+
const cores = Number.parseInt(nproc.stdout.trim(), 10);
|
|
13385
|
+
if (nproc.exitCode !== 0 || !Number.isInteger(cores) || cores <= 0) {
|
|
13386
|
+
warnings.push(
|
|
13387
|
+
"Could not count this machine's CPUs, so the CPU you keep is weighted, not capped."
|
|
13388
|
+
);
|
|
13389
|
+
return false;
|
|
13390
|
+
}
|
|
13391
|
+
const percent = podCpuQuotaPercent(cores, reserved);
|
|
13392
|
+
if (percent === null) {
|
|
13393
|
+
warnings.push(
|
|
13394
|
+
`The CPU you keep (${reserve}) leaves no CPU for builds on this ${cores}-CPU machine. Lower it in your Conveyor profile.`
|
|
13395
|
+
);
|
|
13396
|
+
return false;
|
|
13397
|
+
}
|
|
13398
|
+
const set = await setQuota(exec, `${percent}%`);
|
|
13399
|
+
if (set.exitCode !== 0) {
|
|
13400
|
+
warnings.push(
|
|
13401
|
+
`Could not cap pod CPU at ${percent / 100} CPUs: ${set.stderr.trim().slice(0, 200)}`
|
|
13402
|
+
);
|
|
13403
|
+
return false;
|
|
13404
|
+
}
|
|
13405
|
+
return true;
|
|
13406
|
+
}
|
|
13407
|
+
|
|
13408
|
+
// src/daemon/registry-auth.ts
|
|
13409
|
+
init_dist();
|
|
13410
|
+
init_paths();
|
|
13411
|
+
var LOCAL_REGISTRY_AUTH_HOSTS = [
|
|
13412
|
+
PERSONAL_LOCAL_REGISTRY_HOST,
|
|
13413
|
+
new URL(PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT).host
|
|
13414
|
+
];
|
|
13415
|
+
function registryAuthConfigured(file, credentials) {
|
|
13416
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.every((host) => {
|
|
13417
|
+
const auth = file.configs?.[host]?.auth;
|
|
13418
|
+
return auth?.username === credentials.username && auth.password === credentials.password && auth.auth === void 0 && auth.identitytoken === void 0;
|
|
13419
|
+
});
|
|
13420
|
+
}
|
|
13421
|
+
function withLocalRegistryAuth(file, credentials) {
|
|
13422
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
13423
|
+
(next, host) => upsertRegistryAuth(next, host, {
|
|
13424
|
+
username: credentials.username,
|
|
13425
|
+
password: credentials.password
|
|
13426
|
+
}),
|
|
13427
|
+
file
|
|
13428
|
+
);
|
|
13429
|
+
}
|
|
13430
|
+
function monotonicNowUs() {
|
|
13431
|
+
return Number(process.hrtime.bigint() / 1000n);
|
|
13432
|
+
}
|
|
13433
|
+
async function k3sStartedAtMs(exec, nowMs, monotonicNow = monotonicNowUs) {
|
|
13434
|
+
const result = await exec(
|
|
13435
|
+
"systemctl",
|
|
13436
|
+
["show", "k3s", "--property=ActiveEnterTimestampMonotonic", "--value"],
|
|
13437
|
+
{ timeoutMs: 1e4 }
|
|
13438
|
+
).catch(() => null);
|
|
13439
|
+
if (!result || result.exitCode !== 0) return null;
|
|
13440
|
+
const startedUs = Number.parseInt(result.stdout.trim(), 10);
|
|
13441
|
+
if (!Number.isFinite(startedUs) || startedUs <= 0) return null;
|
|
13442
|
+
return nowMs - (monotonicNow() - startedUs) / 1e3;
|
|
13443
|
+
}
|
|
13444
|
+
async function settleRegistryAuth(ctx, credentials, written) {
|
|
13445
|
+
const now = (ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
13446
|
+
const raw = await ctx.fs.readFile(LOCAL_REGISTRY_AUTH_STATE_FILE);
|
|
13447
|
+
const credential = credentialFingerprint(credentials);
|
|
13448
|
+
const stored = parseRegistryAuthState(raw);
|
|
13449
|
+
const state = stored?.credential === credential ? stored : { credential, writtenAt: null, enforced: false };
|
|
13450
|
+
if (!state.enforced && (written || state.writtenAt === null)) {
|
|
13451
|
+
state.writtenAt = now.toISOString();
|
|
13452
|
+
}
|
|
13453
|
+
if (!state.enforced && state.writtenAt !== null) {
|
|
13454
|
+
const startedAt = await k3sStartedAtMs(ctx.exec, now.getTime(), ctx.monotonicNowUs);
|
|
13455
|
+
if (startedAt !== null && startedAt > Date.parse(state.writtenAt)) state.enforced = true;
|
|
13456
|
+
}
|
|
13457
|
+
const next = `${JSON.stringify(state)}
|
|
13458
|
+
`;
|
|
13459
|
+
if (next !== raw) await ctx.fs.writeFile(LOCAL_REGISTRY_AUTH_STATE_FILE, next, 384);
|
|
13460
|
+
return state.enforced;
|
|
13461
|
+
}
|
|
13462
|
+
|
|
12969
13463
|
// src/lib/isolation.ts
|
|
12970
13464
|
init_dist();
|
|
12971
13465
|
|
|
@@ -13234,17 +13728,36 @@ async function removeLocalRegistry(ports) {
|
|
|
13234
13728
|
`Could not remove the local image registry from the cluster: ${removed.stderr.trim().slice(0, 200)}`
|
|
13235
13729
|
);
|
|
13236
13730
|
}
|
|
13731
|
+
for (const [namespace, name2] of [
|
|
13732
|
+
[PERSONAL_LOCAL_REGISTRY_NAMESPACE, LOCAL_REGISTRY_HTPASSWD_SECRET],
|
|
13733
|
+
[PERSONAL_BAKE_NAMESPACE, PERSONAL_LOCAL_REGISTRY_PUSH_SECRET]
|
|
13734
|
+
]) {
|
|
13735
|
+
const secret = await ports.exec(
|
|
13736
|
+
"k3s",
|
|
13737
|
+
["kubectl", "-n", namespace, "delete", "secret", name2, "--ignore-not-found"],
|
|
13738
|
+
{ timeoutMs: 6e4 }
|
|
13739
|
+
);
|
|
13740
|
+
if (secret.exitCode !== 0) {
|
|
13741
|
+
ports.log(
|
|
13742
|
+
`Could not remove the local image registry's Secret ${namespace}/${name2}: ${secret.stderr.trim().slice(0, 200)}`
|
|
13743
|
+
);
|
|
13744
|
+
}
|
|
13745
|
+
}
|
|
13237
13746
|
const raw = await ports.fs.readFile(REGISTRIES_FILE);
|
|
13238
13747
|
const parsed = parseRegistries(raw);
|
|
13239
|
-
|
|
13748
|
+
const next = LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
13749
|
+
removeRegistryAuth,
|
|
13750
|
+
removeRegistryMirror(parsed, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
13751
|
+
);
|
|
13752
|
+
if (serializeRegistries(next) === serializeRegistries(parsed)) return;
|
|
13240
13753
|
const backup = await writeRegistriesWithBackup(
|
|
13241
13754
|
ports.fs,
|
|
13242
13755
|
raw,
|
|
13243
|
-
|
|
13756
|
+
next,
|
|
13244
13757
|
(ports.now ?? (() => /* @__PURE__ */ new Date()))()
|
|
13245
13758
|
);
|
|
13246
13759
|
ports.log(
|
|
13247
|
-
`Removed the local image registry's mirror from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
13760
|
+
`Removed the local image registry's mirror and credential from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
13248
13761
|
);
|
|
13249
13762
|
}
|
|
13250
13763
|
async function removeIsolation(ports) {
|
|
@@ -13301,6 +13814,10 @@ async function uninstall(ports, options = {}) {
|
|
|
13301
13814
|
}
|
|
13302
13815
|
await removeLocalRegistry(ports);
|
|
13303
13816
|
await removeIsolation(ports);
|
|
13817
|
+
const lifted = await liftPodCpuQuota(ports.exec).catch(
|
|
13818
|
+
(error) => error instanceof Error ? error.message : String(error)
|
|
13819
|
+
);
|
|
13820
|
+
if (lifted) ports.log(`Could not lift Conveyor's CPU cap on kubepods.slice: ${lifted}`);
|
|
13304
13821
|
for (const path of OWNED_FILES) {
|
|
13305
13822
|
await ports.fs.removeFile(path);
|
|
13306
13823
|
result.removedFiles.push(path);
|
|
@@ -13353,6 +13870,74 @@ async function uninstall(ports, options = {}) {
|
|
|
13353
13870
|
return result;
|
|
13354
13871
|
}
|
|
13355
13872
|
|
|
13873
|
+
// src/lib/bins.ts
|
|
13874
|
+
var INSTALLER_PATHS = {
|
|
13875
|
+
bun: "/root/.bun/bin/bun",
|
|
13876
|
+
cli: "/root/.bun/bin/conveyor-k3",
|
|
13877
|
+
cloudflared: "/usr/local/bin/cloudflared"
|
|
13878
|
+
};
|
|
13879
|
+
async function whichOrNull(exec, bin) {
|
|
13880
|
+
const result = await exec("which", [bin]).catch(() => null);
|
|
13881
|
+
if (!result || result.exitCode !== 0) return null;
|
|
13882
|
+
const path = result.stdout.trim().split("\n")[0]?.trim();
|
|
13883
|
+
return path && path.startsWith("/") ? path : null;
|
|
13884
|
+
}
|
|
13885
|
+
async function resolveBinaries(exec, state, selfPath) {
|
|
13886
|
+
const recorded = state.bins;
|
|
13887
|
+
const cliSelf = selfPath?.startsWith("/") ? selfPath : null;
|
|
13888
|
+
return {
|
|
13889
|
+
bun: recorded?.bun ?? await whichOrNull(exec, "bun") ?? INSTALLER_PATHS.bun,
|
|
13890
|
+
// `cli` prefers the running process's own path: if this code is executing,
|
|
13891
|
+
// that path is by definition correct.
|
|
13892
|
+
cli: cliSelf ?? recorded?.cli ?? await whichOrNull(exec, "conveyor-k3") ?? INSTALLER_PATHS.cli,
|
|
13893
|
+
cloudflared: recorded?.cloudflared ?? await whichOrNull(exec, "cloudflared") ?? INSTALLER_PATHS.cloudflared
|
|
13894
|
+
};
|
|
13895
|
+
}
|
|
13896
|
+
var CLI_PATH_LINK = "/usr/local/bin/conveyor-k3";
|
|
13897
|
+
async function linkCliOnPath(exec, target2 = INSTALLER_PATHS.cli) {
|
|
13898
|
+
if (target2 === CLI_PATH_LINK) return { linked: false };
|
|
13899
|
+
const present = await exec("test", ["-x", target2]).catch(() => null);
|
|
13900
|
+
if (present?.exitCode !== 0) return { linked: false };
|
|
13901
|
+
const link = await exec("ln", ["-sfn", target2, CLI_PATH_LINK]).catch(() => null);
|
|
13902
|
+
if (link?.exitCode === 0) return { linked: true };
|
|
13903
|
+
return {
|
|
13904
|
+
linked: false,
|
|
13905
|
+
problem: `Could not link ${CLI_PATH_LINK} to ${target2}: ${link?.stderr.trim().slice(0, 200) || "ln failed"}`
|
|
13906
|
+
};
|
|
13907
|
+
}
|
|
13908
|
+
var NPM_GLOBAL_PACKAGE_DIRS = [
|
|
13909
|
+
"/usr/local/lib/node_modules/@rallycry/conveyor-k3",
|
|
13910
|
+
"/usr/lib/node_modules/@rallycry/conveyor-k3"
|
|
13911
|
+
];
|
|
13912
|
+
async function npmCopyHint(readFile9) {
|
|
13913
|
+
for (const dir of NPM_GLOBAL_PACKAGE_DIRS) {
|
|
13914
|
+
if (await readFile9(`${dir}/package.json`) !== null) {
|
|
13915
|
+
return `An npm copy of conveyor-k3 is installed at ${dir}. Conveyor runs and updates its own copy (${INSTALLER_PATHS.cli}, linked from ${CLI_PATH_LINK}), so you can remove it: sudo npm uninstall -g @rallycry/conveyor-k3`;
|
|
13916
|
+
}
|
|
13917
|
+
}
|
|
13918
|
+
return null;
|
|
13919
|
+
}
|
|
13920
|
+
|
|
13921
|
+
// src/lib/cloudflared.ts
|
|
13922
|
+
var PACKAGE_MANAGER_REFUSAL = "installed by a package manager";
|
|
13923
|
+
async function resolveRealPath(exec, bin) {
|
|
13924
|
+
const result = await exec("readlink", ["-f", bin]).catch(() => null);
|
|
13925
|
+
const resolved = result?.exitCode === 0 ? result.stdout.trim() : "";
|
|
13926
|
+
return resolved.startsWith("/") ? resolved : bin;
|
|
13927
|
+
}
|
|
13928
|
+
async function cloudflaredUpdater(exec, bin) {
|
|
13929
|
+
const real = await resolveRealPath(exec, bin);
|
|
13930
|
+
const owner = await exec("dpkg", ["-S", real]).catch(() => null);
|
|
13931
|
+
return owner?.exitCode === 0 ? "package-manager" : "conveyor";
|
|
13932
|
+
}
|
|
13933
|
+
function isPackageManagerRefusal(output) {
|
|
13934
|
+
return `${output.stdout}
|
|
13935
|
+
${output.stderr}`.toLowerCase().includes(PACKAGE_MANAGER_REFUSAL);
|
|
13936
|
+
}
|
|
13937
|
+
function describeCloudflaredUpdater(updater) {
|
|
13938
|
+
return updater === "package-manager" ? "cloudflared is updated by your package manager, not by Conveyor" : "cloudflared is updated by Conveyor's daily update";
|
|
13939
|
+
}
|
|
13940
|
+
|
|
13356
13941
|
// src/commands/update.ts
|
|
13357
13942
|
init_paths();
|
|
13358
13943
|
var CLOUDFLARED_UPDATED_EXIT = 11;
|
|
@@ -13441,7 +14026,22 @@ async function updatePackage(ports, result, targetVersion) {
|
|
|
13441
14026
|
await ports.exec("systemctl", ["restart", DAEMON_UNIT]);
|
|
13442
14027
|
}
|
|
13443
14028
|
async function updateCloudflared(ports, result) {
|
|
14029
|
+
const packaged = () => {
|
|
14030
|
+
result.cloudflaredUpdater = "package-manager";
|
|
14031
|
+
result.notes.push(
|
|
14032
|
+
`cloudflared update skipped: ${describeCloudflaredUpdater("package-manager")}`
|
|
14033
|
+
);
|
|
14034
|
+
};
|
|
14035
|
+
if (await cloudflaredUpdater(ports.exec, ports.bins.cloudflared) === "package-manager") {
|
|
14036
|
+
packaged();
|
|
14037
|
+
return;
|
|
14038
|
+
}
|
|
13444
14039
|
const update = await ports.exec(ports.bins.cloudflared, ["update"], { timeoutMs: 5 * 6e4 });
|
|
14040
|
+
if (isPackageManagerRefusal(update)) {
|
|
14041
|
+
packaged();
|
|
14042
|
+
return;
|
|
14043
|
+
}
|
|
14044
|
+
result.cloudflaredUpdater = "conveyor";
|
|
13445
14045
|
if (update.exitCode !== CLOUDFLARED_UPDATED_EXIT) return;
|
|
13446
14046
|
result.cloudflaredUpdated = true;
|
|
13447
14047
|
ports.log("cloudflared was updated; restarting the tunnel.");
|
|
@@ -13451,10 +14051,13 @@ async function runUpdate(ports, decision, channel, targetVersion) {
|
|
|
13451
14051
|
const result = {
|
|
13452
14052
|
packageUpdated: false,
|
|
13453
14053
|
cloudflaredUpdated: false,
|
|
14054
|
+
cloudflaredUpdater: null,
|
|
13454
14055
|
k3sUpgraded: false,
|
|
13455
14056
|
notes: []
|
|
13456
14057
|
};
|
|
13457
14058
|
await updatePackage(ports, result, targetVersion);
|
|
14059
|
+
const linked = await linkCliOnPath(ports.exec);
|
|
14060
|
+
if (linked.problem) result.notes.push(linked.problem);
|
|
13458
14061
|
await updateCloudflared(ports, result);
|
|
13459
14062
|
if (!decision.upgrade) {
|
|
13460
14063
|
result.notes.push(`k3s upgrade skipped: ${decision.reason}`);
|
|
@@ -13656,7 +14259,7 @@ async function ensureCaPool(ports) {
|
|
|
13656
14259
|
const existing = await ports.fs.readFile(CA_POOL_PATH);
|
|
13657
14260
|
if (existing !== ca) await ports.fs.writeFile(CA_POOL_PATH, ca, 420);
|
|
13658
14261
|
}
|
|
13659
|
-
async function
|
|
14262
|
+
async function ensureServiceAccount(ports) {
|
|
13660
14263
|
await runOrThrow(ports.exec, "k3s", [
|
|
13661
14264
|
"kubectl",
|
|
13662
14265
|
"create",
|
|
@@ -13685,6 +14288,9 @@ async function mintServiceAccountToken(ports) {
|
|
|
13685
14288
|
stdin: result.stdout
|
|
13686
14289
|
});
|
|
13687
14290
|
});
|
|
14291
|
+
}
|
|
14292
|
+
async function mintServiceAccountToken(ports) {
|
|
14293
|
+
await ensureServiceAccount(ports);
|
|
13688
14294
|
const token = await runOrThrow(ports.exec, "k3s", [
|
|
13689
14295
|
"kubectl",
|
|
13690
14296
|
"-n",
|
|
@@ -13752,13 +14358,13 @@ function buildConveyorSocketOptions(auth) {
|
|
|
13752
14358
|
|
|
13753
14359
|
// src/daemon/control-endpoint.ts
|
|
13754
14360
|
import { lookup as dnsLookup } from "dns";
|
|
13755
|
-
import { existsSync, readFileSync } from "fs";
|
|
14361
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
13756
14362
|
import { Agent } from "https";
|
|
13757
14363
|
var CONTROL_FILE = "/etc/conveyor-vm/control.json";
|
|
13758
14364
|
var CONTROL_ALIAS_IP = /^192\.168\.5\.24[0-7]$/;
|
|
13759
14365
|
function readControlFile(path) {
|
|
13760
14366
|
try {
|
|
13761
|
-
return
|
|
14367
|
+
return readFileSync2(path, "utf8");
|
|
13762
14368
|
} catch {
|
|
13763
14369
|
return null;
|
|
13764
14370
|
}
|
|
@@ -13804,8 +14410,8 @@ function controlPlaneRoute(apiUrl, readFile9 = readControlFile, fileExists = exi
|
|
|
13804
14410
|
}
|
|
13805
14411
|
|
|
13806
14412
|
// src/daemon/device-proof.ts
|
|
13807
|
-
import { lstatSync, readFileSync as
|
|
13808
|
-
import { createPrivateKey, randomBytes, sign } from "crypto";
|
|
14413
|
+
import { lstatSync, readFileSync as readFileSync3 } from "fs";
|
|
14414
|
+
import { createPrivateKey, randomBytes as randomBytes2, sign } from "crypto";
|
|
13809
14415
|
var DEVICE_KEY_PATH = "/etc/conveyor/device-key.pem";
|
|
13810
14416
|
function privateDeviceKey(path, ownerUid) {
|
|
13811
14417
|
let stat2;
|
|
@@ -13819,7 +14425,7 @@ function privateDeviceKey(path, ownerUid) {
|
|
|
13819
14425
|
throw new Error("Conveyor device key must be a regular file.");
|
|
13820
14426
|
if ((stat2.mode & 511) !== 384 || stat2.uid !== ownerUid)
|
|
13821
14427
|
throw new Error("Conveyor device key must be owned by root with mode 0600.");
|
|
13822
|
-
const pem =
|
|
14428
|
+
const pem = readFileSync3(path, "utf8");
|
|
13823
14429
|
if (Buffer.byteLength(pem) > 16 * 1024) throw new Error("Conveyor device key is too large.");
|
|
13824
14430
|
const key = createPrivateKey(pem);
|
|
13825
14431
|
if (key.asymmetricKeyType !== "ed25519")
|
|
@@ -13829,7 +14435,7 @@ function privateDeviceKey(path, ownerUid) {
|
|
|
13829
14435
|
function createClusterProof(apiUrl, clusterToken, key) {
|
|
13830
14436
|
const origin = new URL(apiUrl).origin;
|
|
13831
14437
|
const timestamp = Date.now();
|
|
13832
|
-
const nonce =
|
|
14438
|
+
const nonce = randomBytes2(32).toString("base64url");
|
|
13833
14439
|
const message3 = `conveyor-device-v1
|
|
13834
14440
|
${origin}
|
|
13835
14441
|
${clusterToken}
|
|
@@ -14003,7 +14609,7 @@ var ClusterConnection = class {
|
|
|
14003
14609
|
};
|
|
14004
14610
|
|
|
14005
14611
|
// src/lib/manifest-guard.ts
|
|
14006
|
-
import { isMap, isScalar, parseAllDocuments } from "yaml";
|
|
14612
|
+
import { isAlias, isMap, isScalar, parseAllDocuments } from "yaml";
|
|
14007
14613
|
var ALLOWED_MANIFEST_KINDS = [
|
|
14008
14614
|
"Namespace",
|
|
14009
14615
|
"Role",
|
|
@@ -14015,6 +14621,14 @@ var ALLOWED_MANIFEST_KINDS = [
|
|
|
14015
14621
|
var ALLOWED = new Set(ALLOWED_MANIFEST_KINDS);
|
|
14016
14622
|
var MISSING_KIND = "(missing kind)";
|
|
14017
14623
|
var UNPARSEABLE = "(unparseable)";
|
|
14624
|
+
var LIST_SHAPED = "(list-shaped document)";
|
|
14625
|
+
function isListShaped(map) {
|
|
14626
|
+
return map.items.some(({ key }) => {
|
|
14627
|
+
if (isAlias(key)) return true;
|
|
14628
|
+
const name2 = isScalar(key) ? key.value : key;
|
|
14629
|
+
return typeof name2 === "string" && (name2 === "<<" || name2.toLowerCase() === "items");
|
|
14630
|
+
});
|
|
14631
|
+
}
|
|
14018
14632
|
function findDisallowedKinds(manifest) {
|
|
14019
14633
|
let documents;
|
|
14020
14634
|
try {
|
|
@@ -14037,6 +14651,10 @@ function findDisallowedKinds(manifest) {
|
|
|
14037
14651
|
record2(MISSING_KIND);
|
|
14038
14652
|
continue;
|
|
14039
14653
|
}
|
|
14654
|
+
if (isListShaped(doc.contents)) {
|
|
14655
|
+
record2(LIST_SHAPED);
|
|
14656
|
+
continue;
|
|
14657
|
+
}
|
|
14040
14658
|
const kind = doc.get("kind");
|
|
14041
14659
|
if (typeof kind !== "string" || kind.length === 0) {
|
|
14042
14660
|
record2(MISSING_KIND);
|
|
@@ -14046,14 +14664,64 @@ function findDisallowedKinds(manifest) {
|
|
|
14046
14664
|
}
|
|
14047
14665
|
return disallowed;
|
|
14048
14666
|
}
|
|
14667
|
+
function withoutKind(manifest, kind) {
|
|
14668
|
+
return parseAllDocuments(manifest).filter((doc) => !(isMap(doc.contents) && doc.get("kind") === kind)).map((doc) => doc.toString()).join("---\n");
|
|
14669
|
+
}
|
|
14670
|
+
function declaresPriorityClass(manifest, name2, value) {
|
|
14671
|
+
return parseAllDocuments(manifest).some(
|
|
14672
|
+
(doc) => isMap(doc.contents) && doc.get("kind") === "PriorityClass" && doc.getIn(["metadata", "name"]) === name2 && doc.get("value") === value
|
|
14673
|
+
);
|
|
14674
|
+
}
|
|
14049
14675
|
|
|
14050
14676
|
// src/daemon/reconcile.ts
|
|
14051
14677
|
init_safe_arg();
|
|
14678
|
+
|
|
14679
|
+
// src/daemon/kubelet-upkeep.ts
|
|
14052
14680
|
init_paths();
|
|
14681
|
+
function ownerRestartWarning(change) {
|
|
14682
|
+
return `${change} Conveyor did not install this server's k3s, so it never restarts it: run \`sudo systemctl restart k3s\` when no build runs.`;
|
|
14683
|
+
}
|
|
14684
|
+
async function reconcileKubelet(ctx, desired, result, managed) {
|
|
14685
|
+
const ownerConfig = await ctx.fs.readFile(K3S_OWNER_CONFIG);
|
|
14686
|
+
const rendered = renderK3sDropIn(desired.kubelet, ownerConfig, { disableComponents: managed });
|
|
14687
|
+
result.skippedKubeletFlags = rendered.skipped;
|
|
14688
|
+
result.effectiveImageGcHighThreshold = rendered.skipped.includes("image-gc-high-threshold") ? ownerImageGcHighThreshold(ownerConfig) : desired.kubelet.imageGcHighThreshold;
|
|
14689
|
+
for (const flag of rendered.skipped) {
|
|
14690
|
+
result.warnings.push(
|
|
14691
|
+
`Left \`${flag}\` to your own /etc/rancher/k3s/config.yaml \u2014 Conveyor does not override a setting you chose.`
|
|
14692
|
+
);
|
|
14693
|
+
}
|
|
14694
|
+
const current = await ctx.fs.readFile(K3S_CONFIG_DROPIN);
|
|
14695
|
+
if (rendered.content === null) {
|
|
14696
|
+
if (current !== null) await ctx.fs.removeFile(K3S_CONFIG_DROPIN);
|
|
14697
|
+
return;
|
|
14698
|
+
}
|
|
14699
|
+
if (current === rendered.content) return;
|
|
14700
|
+
await ctx.fs.ensureDir(K3S_CONFIG_DROPIN.slice(0, K3S_CONFIG_DROPIN.lastIndexOf("/")));
|
|
14701
|
+
await ctx.fs.writeFile(K3S_CONFIG_DROPIN, rendered.content, 420);
|
|
14702
|
+
if (!managed) {
|
|
14703
|
+
result.warnings.push(ownerRestartWarning("Kubelet settings changed."));
|
|
14704
|
+
return;
|
|
14705
|
+
}
|
|
14706
|
+
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
14707
|
+
result.warnings.push(
|
|
14708
|
+
`Kubelet settings changed but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
14709
|
+
);
|
|
14710
|
+
return;
|
|
14711
|
+
}
|
|
14712
|
+
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
14713
|
+
if (restart.exitCode === 0) {
|
|
14714
|
+
result.restartedK3s = true;
|
|
14715
|
+
} else {
|
|
14716
|
+
result.warnings.push(
|
|
14717
|
+
`Could not restart k3s to apply new kubelet settings: ${restart.stderr.trim().slice(0, 200)}`
|
|
14718
|
+
);
|
|
14719
|
+
}
|
|
14720
|
+
}
|
|
14053
14721
|
|
|
14054
14722
|
// src/daemon/local-registry-upkeep.ts
|
|
14055
14723
|
init_dist();
|
|
14056
|
-
import { createHash } from "crypto";
|
|
14724
|
+
import { createHash as createHash3 } from "crypto";
|
|
14057
14725
|
init_paths();
|
|
14058
14726
|
function nowDate() {
|
|
14059
14727
|
return /* @__PURE__ */ new Date();
|
|
@@ -14062,33 +14730,47 @@ function mirrorConfigured(raw) {
|
|
|
14062
14730
|
const endpoints = mirrorEndpoints(parseRegistries(raw), PERSONAL_LOCAL_REGISTRY_HOST);
|
|
14063
14731
|
return endpoints.length === 1 && endpoints[0] === PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT;
|
|
14064
14732
|
}
|
|
14065
|
-
async function applyUnlessCollecting(ctx) {
|
|
14733
|
+
async function applyUnlessCollecting(ctx, auth) {
|
|
14066
14734
|
const deployment = await readRegistryDeployment(ctx.exec);
|
|
14067
14735
|
if (!deployment) throw new Error("the registry's Deployment could not be read");
|
|
14068
14736
|
if (gcMarkIsLive(deployment.mark, (ctx.now ?? nowDate)().getTime(), ctx.isAlive)) return false;
|
|
14069
14737
|
if (deployment.mark !== null && !await deleteSweepJob(ctx.exec)) {
|
|
14070
14738
|
throw new Error("a stopped cleanup's Job could not be deleted, so the registry stays stopped");
|
|
14071
14739
|
}
|
|
14072
|
-
await kubectlApply(ctx.exec, renderLocalRegistryManifests());
|
|
14740
|
+
await kubectlApply(ctx.exec, renderLocalRegistryManifests(auth));
|
|
14073
14741
|
if (deployment.mark !== null) await clearGcMark(ctx.exec);
|
|
14074
14742
|
return true;
|
|
14075
14743
|
}
|
|
14076
14744
|
async function deployLocalRegistry(ctx) {
|
|
14077
14745
|
await ctx.fs.ensureDir(LOCAL_REGISTRY_DATA_DIR, 448);
|
|
14078
|
-
const
|
|
14746
|
+
const credentials = await loadOrCreateRegistryCredentials(ctx.fs, ctx.random);
|
|
14079
14747
|
const raw = await ctx.fs.readFile(REGISTRIES_FILE);
|
|
14080
|
-
|
|
14081
|
-
const
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14748
|
+
let file = parseRegistries(raw);
|
|
14749
|
+
const mirrorWritten = !mirrorConfigured(raw);
|
|
14750
|
+
const authWritten = !registryAuthConfigured(file, credentials);
|
|
14751
|
+
let backupPath = null;
|
|
14752
|
+
if (mirrorWritten) {
|
|
14753
|
+
file = upsertRegistryMirror(file, PERSONAL_LOCAL_REGISTRY_HOST, [
|
|
14754
|
+
PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT
|
|
14755
|
+
]);
|
|
14756
|
+
}
|
|
14757
|
+
if (authWritten) file = withLocalRegistryAuth(file, credentials);
|
|
14758
|
+
if (mirrorWritten || authWritten) {
|
|
14759
|
+
backupPath = await writeRegistriesWithBackup(ctx.fs, raw, file, (ctx.now ?? nowDate)());
|
|
14760
|
+
}
|
|
14761
|
+
const authEnforced = await settleRegistryAuth(ctx, credentials, authWritten);
|
|
14762
|
+
await kubectlApply(ctx.exec, renderLocalRegistrySecrets(credentials));
|
|
14763
|
+
const applied = await applyUnlessCollecting(ctx, authEnforced ? credentials : null);
|
|
14764
|
+
return { applied, mirrorWritten, authWritten, backupPath, authEnforced };
|
|
14086
14765
|
}
|
|
14087
14766
|
async function readMirrorState(fs2) {
|
|
14088
14767
|
const raw = await fs2.readFile(REGISTRIES_FILE);
|
|
14089
|
-
const
|
|
14768
|
+
const authPending = !await registryAuthEnforced((path) => fs2.readFile(path));
|
|
14769
|
+
const fingerprint = createHash3("sha256").update(raw ?? "").digest("hex");
|
|
14090
14770
|
if (!mirrorConfigured(raw)) return { state: "absent", fingerprint };
|
|
14091
|
-
if (await mirrorLoaded((path) => fs2.readFile(path)))
|
|
14771
|
+
if (!authPending && await mirrorLoaded((path) => fs2.readFile(path))) {
|
|
14772
|
+
return { state: "loaded", fingerprint };
|
|
14773
|
+
}
|
|
14092
14774
|
const tried = (await fs2.readFile(LOCAL_REGISTRY_MIRROR_RESTART_FILE))?.trim() === fingerprint;
|
|
14093
14775
|
return { state: tried ? "tried" : "owed", fingerprint };
|
|
14094
14776
|
}
|
|
@@ -14100,14 +14782,14 @@ async function loadRegistryMirror(ctx) {
|
|
|
14100
14782
|
if (state === "tried") {
|
|
14101
14783
|
return {
|
|
14102
14784
|
restarted: false,
|
|
14103
|
-
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}.`
|
|
14785
|
+
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}.`
|
|
14104
14786
|
};
|
|
14105
14787
|
}
|
|
14106
14788
|
if (state !== "owed") return { restarted: false, warning: null };
|
|
14107
14789
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
14108
14790
|
return {
|
|
14109
14791
|
restarted: false,
|
|
14110
|
-
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.`
|
|
14792
|
+
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.`
|
|
14111
14793
|
};
|
|
14112
14794
|
}
|
|
14113
14795
|
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
@@ -14138,7 +14820,18 @@ async function stdoutOf(exec, bin, args) {
|
|
|
14138
14820
|
const run = await exec(bin, args, { timeoutMs: 1e4 }).catch(() => null);
|
|
14139
14821
|
return run && run.exitCode === 0 ? run.stdout : null;
|
|
14140
14822
|
}
|
|
14823
|
+
async function dropInDirExists(exec) {
|
|
14824
|
+
const run = await exec("test", ["-d", K3S_CONFIG_DROPIN_DIR], { timeoutMs: 1e4 }).catch(
|
|
14825
|
+
() => null
|
|
14826
|
+
);
|
|
14827
|
+
if (run === null) return null;
|
|
14828
|
+
if (run.exitCode === 0) return true;
|
|
14829
|
+
return run.exitCode === 1 && run.stderr.trim() === "" ? false : null;
|
|
14830
|
+
}
|
|
14141
14831
|
async function readK3sConfigs(ctx) {
|
|
14832
|
+
const dirExists = await dropInDirExists(ctx.exec);
|
|
14833
|
+
if (dirExists === null) return null;
|
|
14834
|
+
if (!dirExists) return [await ctx.fs.readFile(K3S_OWNER_CONFIG)];
|
|
14142
14835
|
const listed = await stdoutOf(ctx.exec, "find", [
|
|
14143
14836
|
"-L",
|
|
14144
14837
|
K3S_CONFIG_DROPIN_DIR,
|
|
@@ -14432,7 +15125,7 @@ async function reportLocalImages(exec, inventory, wantedImages, cache) {
|
|
|
14432
15125
|
if (!inventory) return void 0;
|
|
14433
15126
|
const wanted = new Set(wantedImages);
|
|
14434
15127
|
const held = inventory.images.flatMap(
|
|
14435
|
-
(image) => (image.repoTags ?? []).filter((ref) => ref.startsWith("localhost/") && isImageRef(ref)).map((ref) => ({ ref, imageId: image.id }))
|
|
15128
|
+
(image) => (image.repoTags ?? []).filter((ref) => (ref.startsWith("localhost/") || wanted.has(ref)) && isImageRef(ref)).map((ref) => ({ ref, imageId: image.id }))
|
|
14436
15129
|
);
|
|
14437
15130
|
held.sort(
|
|
14438
15131
|
(a, b) => Number(wanted.has(b.ref)) - Number(wanted.has(a.ref)) || (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0)
|
|
@@ -14504,14 +15197,22 @@ async function readImageInventory(exec) {
|
|
|
14504
15197
|
}
|
|
14505
15198
|
return { images, referencedIds };
|
|
14506
15199
|
}
|
|
15200
|
+
var CRICTL_RMI_TIMEOUT = "120s";
|
|
15201
|
+
var RMI_EXEC_TIMEOUT_MS = 15e4;
|
|
15202
|
+
var DEADLINE_EXCEEDED = "context deadline exceeded";
|
|
14507
15203
|
async function pruneStrandedImages(exec, inventory, wantedImages) {
|
|
14508
15204
|
const candidates = selectStrandedImages(inventory, wantedImages);
|
|
14509
|
-
const result = { removed: 0, bytesReclaimed: 0, failures: [] };
|
|
15205
|
+
const result = { removed: 0, bytesReclaimed: 0, failures: [], pending: [] };
|
|
14510
15206
|
for (const candidate of candidates) {
|
|
14511
|
-
const removal = await exec("crictl", ["rmi", candidate.id]
|
|
15207
|
+
const removal = await exec("crictl", ["--timeout", CRICTL_RMI_TIMEOUT, "rmi", candidate.id], {
|
|
15208
|
+
timeoutMs: RMI_EXEC_TIMEOUT_MS
|
|
15209
|
+
});
|
|
14512
15210
|
if (removal.exitCode === 0) {
|
|
14513
15211
|
result.removed += 1;
|
|
14514
15212
|
result.bytesReclaimed += candidate.sizeBytes;
|
|
15213
|
+
} else if (`${removal.stderr}
|
|
15214
|
+
${removal.stdout}`.includes(DEADLINE_EXCEEDED)) {
|
|
15215
|
+
result.pending.push(candidate.id);
|
|
14515
15216
|
} else {
|
|
14516
15217
|
result.failures.push(`${candidate.id}: ${removal.stderr.trim().slice(0, 120)}`);
|
|
14517
15218
|
}
|
|
@@ -14613,17 +15314,26 @@ function countPinnedWanted(inventory, wantedImages) {
|
|
|
14613
15314
|
// src/daemon/telemetry.ts
|
|
14614
15315
|
init_paths();
|
|
14615
15316
|
var LOCAL_REGISTRY_WARN_AFTER_MS = 10 * 6e4;
|
|
15317
|
+
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.";
|
|
14616
15318
|
async function reportLocalRegistry(ports, watch2, now = Date.now()) {
|
|
14617
15319
|
if (!watch2) return { fields: {}, warnings: [] };
|
|
14618
|
-
const ready = await
|
|
15320
|
+
const { ready, authEnforced } = await localRegistryStatus({
|
|
15321
|
+
readFile: ports.readFile,
|
|
15322
|
+
fetch: ports.fetch
|
|
15323
|
+
});
|
|
14619
15324
|
const paused = (watch2.pausedUntil ?? 0) > now;
|
|
14620
15325
|
watch2.notReadySince = ready || paused ? null : watch2.notReadySince ?? now;
|
|
14621
15326
|
const stuckMs = watch2.notReadySince === null ? 0 : now - watch2.notReadySince;
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
warnings
|
|
15327
|
+
const warnings = [];
|
|
15328
|
+
if (stuckMs >= LOCAL_REGISTRY_WARN_AFTER_MS) {
|
|
15329
|
+
warnings.push(
|
|
14625
15330
|
`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.`
|
|
14626
|
-
|
|
15331
|
+
);
|
|
15332
|
+
}
|
|
15333
|
+
if (!authEnforced) warnings.push(LOCAL_REGISTRY_OPEN_WARNING);
|
|
15334
|
+
return {
|
|
15335
|
+
fields: { localRegistry: { host: PERSONAL_LOCAL_REGISTRY_HOST, ready, authEnforced } },
|
|
15336
|
+
warnings
|
|
14627
15337
|
};
|
|
14628
15338
|
}
|
|
14629
15339
|
var VM_CAPACITY_FILE = "/etc/conveyor-vm/capacity.json";
|
|
@@ -14746,6 +15456,7 @@ async function collectHeartbeat(ports, state, inventory) {
|
|
|
14746
15456
|
...localImages ? { localImages } : {},
|
|
14747
15457
|
...registry.fields,
|
|
14748
15458
|
...state.isolation ? { isolation: state.isolation } : {},
|
|
15459
|
+
...state.cpuQuotaApplied === void 0 ? {} : { cpuQuotaApplied: state.cpuQuotaApplied },
|
|
14749
15460
|
localWarnings: withinHeartbeatBounds([
|
|
14750
15461
|
...state.localWarnings,
|
|
14751
15462
|
...gcWarning ? [gcWarning] : [],
|
|
@@ -14796,7 +15507,15 @@ async function collect(ports, now) {
|
|
|
14796
15507
|
if (watch2) watch2.pausedUntil = now.getTime() + REGISTRY_GC_MARK_TTL_MS;
|
|
14797
15508
|
try {
|
|
14798
15509
|
const outcome = await collectRegistryGarbage(
|
|
14799
|
-
{
|
|
15510
|
+
{
|
|
15511
|
+
exec,
|
|
15512
|
+
log: log2,
|
|
15513
|
+
requestDesiredState,
|
|
15514
|
+
fetch: fetch2,
|
|
15515
|
+
readFile: (path) => ports.fs.readFile(path),
|
|
15516
|
+
sleep: sleep4,
|
|
15517
|
+
isAlive
|
|
15518
|
+
},
|
|
14800
15519
|
{ dryRun: false }
|
|
14801
15520
|
);
|
|
14802
15521
|
if (outcome.skipped) return outcome.skipped;
|
|
@@ -14842,48 +15561,7 @@ async function runRegistryUpkeep(ports, lastGcAt, maintenanceHour) {
|
|
|
14842
15561
|
}
|
|
14843
15562
|
|
|
14844
15563
|
// src/daemon/reconcile.ts
|
|
14845
|
-
function
|
|
14846
|
-
return `${change} Conveyor did not install this server's k3s, so it never restarts it: run \`sudo systemctl restart k3s\` when no build runs.`;
|
|
14847
|
-
}
|
|
14848
|
-
async function reconcileKubelet(ctx, desired, result, managed) {
|
|
14849
|
-
const ownerConfig = await ctx.fs.readFile(K3S_OWNER_CONFIG);
|
|
14850
|
-
const rendered = renderK3sDropIn(desired.kubelet, ownerConfig, { disableComponents: managed });
|
|
14851
|
-
result.skippedKubeletFlags = rendered.skipped;
|
|
14852
|
-
result.effectiveImageGcHighThreshold = rendered.skipped.includes("image-gc-high-threshold") ? ownerImageGcHighThreshold(ownerConfig) : desired.kubelet.imageGcHighThreshold;
|
|
14853
|
-
for (const flag of rendered.skipped) {
|
|
14854
|
-
result.warnings.push(
|
|
14855
|
-
`Left \`${flag}\` to your own /etc/rancher/k3s/config.yaml \u2014 Conveyor does not override a setting you chose.`
|
|
14856
|
-
);
|
|
14857
|
-
}
|
|
14858
|
-
const current = await ctx.fs.readFile(K3S_CONFIG_DROPIN);
|
|
14859
|
-
if (rendered.content === null) {
|
|
14860
|
-
if (current !== null) await ctx.fs.removeFile(K3S_CONFIG_DROPIN);
|
|
14861
|
-
return;
|
|
14862
|
-
}
|
|
14863
|
-
if (current === rendered.content) return;
|
|
14864
|
-
await ctx.fs.ensureDir(K3S_CONFIG_DROPIN.slice(0, K3S_CONFIG_DROPIN.lastIndexOf("/")));
|
|
14865
|
-
await ctx.fs.writeFile(K3S_CONFIG_DROPIN, rendered.content, 420);
|
|
14866
|
-
if (!managed) {
|
|
14867
|
-
result.warnings.push(ownerRestartWarning("Kubelet settings changed."));
|
|
14868
|
-
return;
|
|
14869
|
-
}
|
|
14870
|
-
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
14871
|
-
result.warnings.push(
|
|
14872
|
-
`Kubelet settings changed but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
14873
|
-
);
|
|
14874
|
-
return;
|
|
14875
|
-
}
|
|
14876
|
-
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
14877
|
-
if (restart.exitCode === 0) {
|
|
14878
|
-
result.restartedK3s = true;
|
|
14879
|
-
} else {
|
|
14880
|
-
result.warnings.push(
|
|
14881
|
-
`Could not restart k3s to apply new kubelet settings: ${restart.stderr.trim().slice(0, 200)}`
|
|
14882
|
-
);
|
|
14883
|
-
}
|
|
14884
|
-
}
|
|
14885
|
-
async function reconcilePriorityClass(ctx, desired, result) {
|
|
14886
|
-
const name2 = assertK8sName(desired.priorityClass.name, "priorityClass.name");
|
|
15564
|
+
async function livePriorityClassValue(ctx, name2) {
|
|
14887
15565
|
const read = await ctx.exec("k3s", [
|
|
14888
15566
|
"kubectl",
|
|
14889
15567
|
"get",
|
|
@@ -14892,30 +15570,59 @@ async function reconcilePriorityClass(ctx, desired, result) {
|
|
|
14892
15570
|
"-o",
|
|
14893
15571
|
"jsonpath={.value}"
|
|
14894
15572
|
]);
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
15573
|
+
const value = Number.parseInt(read.stdout.trim(), 10);
|
|
15574
|
+
return read.exitCode === 0 && Number.isFinite(value) ? value : null;
|
|
15575
|
+
}
|
|
15576
|
+
async function reconcilePriorityClass(ctx, desired, result) {
|
|
15577
|
+
const name2 = assertK8sName(desired.priorityClass.name, "priorityClass.name");
|
|
15578
|
+
const wanted = desired.priorityClass.value;
|
|
15579
|
+
const existingValue = await livePriorityClassValue(ctx, name2);
|
|
15580
|
+
if (existingValue === null || existingValue === wanted) return "ready";
|
|
15581
|
+
if (!declaresPriorityClass(desired.rbacManifest, name2, wanted)) {
|
|
15582
|
+
result.warnings.push(
|
|
15583
|
+
`The \`${name2}\` 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.`
|
|
15584
|
+
);
|
|
15585
|
+
return "kept";
|
|
15586
|
+
}
|
|
14898
15587
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
14899
15588
|
result.warnings.push(
|
|
14900
|
-
`The \`${name2}\` PriorityClass has value ${existingValue}, but Conveyor needs ${
|
|
15589
|
+
`The \`${name2}\` PriorityClass has value ${existingValue}, but Conveyor needs ${wanted}. The value is immutable, so it must be recreated \u2014 deferred because ${busyReason(ctx.runningConveyorPods)}.`
|
|
14901
15590
|
);
|
|
14902
|
-
return;
|
|
15591
|
+
return "kept";
|
|
14903
15592
|
}
|
|
14904
|
-
const removed = await ctx.exec("k3s", [
|
|
15593
|
+
const removed = await ctx.exec("k3s", [
|
|
15594
|
+
"kubectl",
|
|
15595
|
+
"delete",
|
|
15596
|
+
"priorityclass",
|
|
15597
|
+
name2,
|
|
15598
|
+
"--ignore-not-found"
|
|
15599
|
+
]);
|
|
14905
15600
|
if (removed.exitCode !== 0) {
|
|
14906
15601
|
result.warnings.push(
|
|
14907
|
-
`Could not replace the \`${name2}\` PriorityClass (value ${existingValue}, needs ${
|
|
15602
|
+
`Could not replace the \`${name2}\` PriorityClass (value ${existingValue}, needs ${wanted}): ${removed.stderr.trim().slice(0, 160)}`
|
|
14908
15603
|
);
|
|
14909
|
-
return;
|
|
15604
|
+
return "kept";
|
|
14910
15605
|
}
|
|
15606
|
+
return "removed";
|
|
15607
|
+
}
|
|
15608
|
+
async function applyRbac(ctx, desired, result) {
|
|
15609
|
+
const outcome = await reconcilePriorityClass(ctx, desired, result);
|
|
15610
|
+
const manifest = outcome === "kept" ? withoutKind(desired.rbacManifest, "PriorityClass") : desired.rbacManifest;
|
|
14911
15611
|
try {
|
|
14912
|
-
await kubectlApply(ctx.exec,
|
|
14913
|
-
result.recreatedPriorityClass = true;
|
|
15612
|
+
await kubectlApply(ctx.exec, manifest);
|
|
14914
15613
|
} catch (error) {
|
|
15614
|
+
if (outcome !== "removed") throw error;
|
|
15615
|
+
const value = await livePriorityClassValue(ctx, desired.priorityClass.name).catch(() => null);
|
|
15616
|
+
if (value === desired.priorityClass.value) {
|
|
15617
|
+
result.recreatedPriorityClass = true;
|
|
15618
|
+
throw error;
|
|
15619
|
+
}
|
|
14915
15620
|
result.warnings.push(
|
|
14916
|
-
`The \`${
|
|
15621
|
+
`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\`.`
|
|
14917
15622
|
);
|
|
15623
|
+
return;
|
|
14918
15624
|
}
|
|
15625
|
+
if (outcome === "removed") result.recreatedPriorityClass = true;
|
|
14919
15626
|
}
|
|
14920
15627
|
async function step(result, label2, run) {
|
|
14921
15628
|
try {
|
|
@@ -14941,12 +15648,7 @@ async function reconcile(ctx, desired) {
|
|
|
14941
15648
|
`Conveyor sent a cluster manifest containing disallowed kind(s) ${disallowedKinds.join(", ")} \u2014 refused. RBAC and PriorityClass steps skipped; this should never happen, report it.`
|
|
14942
15649
|
);
|
|
14943
15650
|
} else {
|
|
14944
|
-
await step(result, "Could not apply RBAC", () =>
|
|
14945
|
-
await step(
|
|
14946
|
-
result,
|
|
14947
|
-
"Could not reconcile the PriorityClass",
|
|
14948
|
-
() => reconcilePriorityClass(ctx, desired, result)
|
|
14949
|
-
);
|
|
15651
|
+
await step(result, "Could not apply RBAC", () => applyRbac(ctx, desired, result));
|
|
14950
15652
|
}
|
|
14951
15653
|
await step(
|
|
14952
15654
|
result,
|
|
@@ -14978,11 +15680,23 @@ async function reconcile(ctx, desired) {
|
|
|
14978
15680
|
"Could not apply kubelet settings",
|
|
14979
15681
|
() => reconcileKubelet(ctx, desired, result, managed)
|
|
14980
15682
|
);
|
|
15683
|
+
if (desired.isolation !== void 0) {
|
|
15684
|
+
await step(result, "Could not cap pod CPU", async () => {
|
|
15685
|
+
result.cpuQuotaApplied = await reconcilePodCpuQuota(ctx.exec, desired, result.warnings);
|
|
15686
|
+
});
|
|
15687
|
+
if (desired.kubelet.systemReserved?.cpu && result.cpuQuotaApplied === void 0) {
|
|
15688
|
+
result.cpuQuotaApplied = false;
|
|
15689
|
+
}
|
|
15690
|
+
}
|
|
14981
15691
|
if (registry && !result.restartedK3s) {
|
|
14982
15692
|
await step(result, "Could not load the local image registry mirror", async () => {
|
|
14983
15693
|
if (!managed) {
|
|
14984
15694
|
if (await registryMirrorPending(ctx.fs))
|
|
14985
|
-
result.warnings.push(
|
|
15695
|
+
result.warnings.push(
|
|
15696
|
+
ownerRestartWarning(
|
|
15697
|
+
"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."
|
|
15698
|
+
)
|
|
15699
|
+
);
|
|
14986
15700
|
return;
|
|
14987
15701
|
}
|
|
14988
15702
|
const loaded = await loadRegistryMirror(ctx);
|
|
@@ -15039,6 +15753,7 @@ async function enrollMachine(ports, env, cloudflaredBin, recorded = {}) {
|
|
|
15039
15753
|
const enrollment = await connection.enroll();
|
|
15040
15754
|
log2(`Enrolled as ${enrollment.hostname}.`);
|
|
15041
15755
|
await installUnits(ports, enrollment, cloudflaredBin, enrollment.desiredState.maintenance.hour);
|
|
15756
|
+
await ensureServiceAccount(ports);
|
|
15042
15757
|
const runningConveyorPods = await countRunningConveyorPods(exec);
|
|
15043
15758
|
const result = await reconcile({ exec, fs: fs2, runningConveyorPods }, enrollment.desiredState);
|
|
15044
15759
|
for (const warning of result.warnings) log2(warning);
|
|
@@ -15060,30 +15775,6 @@ async function enrollMachine(ports, env, cloudflaredBin, recorded = {}) {
|
|
|
15060
15775
|
for (const line of renderRegistryAuthSummary(missing)) log2(line);
|
|
15061
15776
|
}
|
|
15062
15777
|
|
|
15063
|
-
// src/lib/bins.ts
|
|
15064
|
-
var INSTALLER_PATHS = {
|
|
15065
|
-
bun: "/root/.bun/bin/bun",
|
|
15066
|
-
cli: "/root/.bun/bin/conveyor-k3",
|
|
15067
|
-
cloudflared: "/usr/local/bin/cloudflared"
|
|
15068
|
-
};
|
|
15069
|
-
async function whichOrNull(exec, bin) {
|
|
15070
|
-
const result = await exec("which", [bin]).catch(() => null);
|
|
15071
|
-
if (!result || result.exitCode !== 0) return null;
|
|
15072
|
-
const path = result.stdout.trim().split("\n")[0]?.trim();
|
|
15073
|
-
return path && path.startsWith("/") ? path : null;
|
|
15074
|
-
}
|
|
15075
|
-
async function resolveBinaries(exec, state, selfPath) {
|
|
15076
|
-
const recorded = state.bins;
|
|
15077
|
-
const cliSelf = selfPath?.startsWith("/") ? selfPath : null;
|
|
15078
|
-
return {
|
|
15079
|
-
bun: recorded?.bun ?? await whichOrNull(exec, "bun") ?? INSTALLER_PATHS.bun,
|
|
15080
|
-
// `cli` prefers the running process's own path: if this code is executing,
|
|
15081
|
-
// that path is by definition correct.
|
|
15082
|
-
cli: cliSelf ?? recorded?.cli ?? await whichOrNull(exec, "conveyor-k3") ?? INSTALLER_PATHS.cli,
|
|
15083
|
-
cloudflared: recorded?.cloudflared ?? await whichOrNull(exec, "cloudflared") ?? INSTALLER_PATHS.cloudflared
|
|
15084
|
-
};
|
|
15085
|
-
}
|
|
15086
|
-
|
|
15087
15778
|
// src/lib/guest-vm.ts
|
|
15088
15779
|
async function requireGuestVm(exec) {
|
|
15089
15780
|
try {
|
|
@@ -15171,6 +15862,8 @@ var ClusterDaemon = class {
|
|
|
15171
15862
|
* against k3s's own settings before reporting it.
|
|
15172
15863
|
*/
|
|
15173
15864
|
isolation = void 0;
|
|
15865
|
+
/** Whether the pod slice's CPU cap is in force, from the last reconcile. */
|
|
15866
|
+
cpuQuotaApplied = void 0;
|
|
15174
15867
|
timers = [];
|
|
15175
15868
|
/** When the last store walk started, so one that failed is not retried on every reconcile. */
|
|
15176
15869
|
storeWalkStartedAt = 0;
|
|
@@ -15253,6 +15946,7 @@ var ClusterDaemon = class {
|
|
|
15253
15946
|
);
|
|
15254
15947
|
this.warnings = result.warnings;
|
|
15255
15948
|
this.isolation = result.isolation;
|
|
15949
|
+
this.cpuQuotaApplied = result.cpuQuotaApplied;
|
|
15256
15950
|
this.scheduleIsolationRetry();
|
|
15257
15951
|
for (const warning of result.warnings) this.ports.log(warning);
|
|
15258
15952
|
if (result.prunedNamespaces.length > 0) {
|
|
@@ -15409,6 +16103,11 @@ var ClusterDaemon = class {
|
|
|
15409
16103
|
);
|
|
15410
16104
|
}
|
|
15411
16105
|
for (const failure of result.failures) this.ports.log(`Image prune failed \u2014 ${failure}`);
|
|
16106
|
+
if (result.pending.length > 0) {
|
|
16107
|
+
this.ports.log(
|
|
16108
|
+
`Image removal still in progress for ${result.pending.length} image(s); the next prune re-checks them.`
|
|
16109
|
+
);
|
|
16110
|
+
}
|
|
15412
16111
|
} catch (error) {
|
|
15413
16112
|
this.ports.log(
|
|
15414
16113
|
`Skipping image prune: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -15447,6 +16146,7 @@ var ClusterDaemon = class {
|
|
|
15447
16146
|
localImageHashes: this.localImageHashes,
|
|
15448
16147
|
...registryOn ? { localRegistry: this.registryWatch } : {},
|
|
15449
16148
|
...isolation.isolation ? { isolation: isolation.isolation } : {},
|
|
16149
|
+
cpuQuotaApplied: this.cpuQuotaApplied,
|
|
15450
16150
|
k3sStoreBytes: this.measuredStoreBytes
|
|
15451
16151
|
},
|
|
15452
16152
|
inventory
|
|
@@ -15643,7 +16343,7 @@ var log = (line) => {
|
|
|
15643
16343
|
function resolveVersion() {
|
|
15644
16344
|
try {
|
|
15645
16345
|
const url = new URL("../package.json", import.meta.url);
|
|
15646
|
-
const pkg = JSON.parse(
|
|
16346
|
+
const pkg = JSON.parse(readFileSync6(url, "utf8"));
|
|
15647
16347
|
return pkg.version ?? "0.0.0";
|
|
15648
16348
|
} catch {
|
|
15649
16349
|
return "0.0.0";
|
|
@@ -15662,7 +16362,7 @@ function setupPorts(exec) {
|
|
|
15662
16362
|
async function runSetup(exec) {
|
|
15663
16363
|
await requireRoot(exec);
|
|
15664
16364
|
await requireGuestVm(exec);
|
|
15665
|
-
const env = readCliEnv();
|
|
16365
|
+
const env = readCliEnv(process.env, null);
|
|
15666
16366
|
const ports = setupPorts(exec);
|
|
15667
16367
|
const { cloudflaredBin } = await prepareMachine(ports);
|
|
15668
16368
|
await enrollMachine(ports, env, cloudflaredBin);
|
|
@@ -15672,15 +16372,23 @@ async function runHostSetup(exec) {
|
|
|
15672
16372
|
const { cloudflaredBin } = await prepareMachine(ports);
|
|
15673
16373
|
let env;
|
|
15674
16374
|
try {
|
|
15675
|
-
env = readCliEnv();
|
|
16375
|
+
env = readCliEnv(process.env, null);
|
|
15676
16376
|
} catch {
|
|
15677
16377
|
const state = await readState(nodeFileSystem);
|
|
15678
16378
|
log(
|
|
15679
16379
|
state.enrolled ? `Prepared. This server is already linked as ${state.hostname}; its daemon keeps it current. To move it to another account, run: sudo conveyor-k3 host link --relink` : "Prepared. This server is not linked yet \u2014 run: sudo conveyor-k3 host link --name=<name>"
|
|
15680
16380
|
);
|
|
16381
|
+
await reportCliInstall(exec);
|
|
15681
16382
|
return;
|
|
15682
16383
|
}
|
|
15683
16384
|
await enrollMachine(ports, env, cloudflaredBin, { runtime: "host" });
|
|
16385
|
+
await reportCliInstall(exec);
|
|
16386
|
+
}
|
|
16387
|
+
async function reportCliInstall(exec) {
|
|
16388
|
+
const linked = await linkCliOnPath(exec);
|
|
16389
|
+
if (linked.problem) log(linked.problem);
|
|
16390
|
+
const hint = await npmCopyHint((path) => nodeFileSystem.readFile(path));
|
|
16391
|
+
if (hint) log(hint);
|
|
15684
16392
|
}
|
|
15685
16393
|
function hostPorts(exec) {
|
|
15686
16394
|
return {
|
|
@@ -15755,6 +16463,8 @@ async function runStatus(exec, asJson, notEnrolledHint = "conveyor-k3 setup") {
|
|
|
15755
16463
|
}
|
|
15756
16464
|
const k3s = await exec("k3s", ["--version"]).catch(() => null);
|
|
15757
16465
|
const version = k3s ? parseK3sVersion(k3s.stdout || k3s.stderr) : null;
|
|
16466
|
+
const bins = await resolveBinaries(exec, state, process.argv[1]);
|
|
16467
|
+
const cloudflaredUpdates = await cloudflaredUpdater(exec, bins.cloudflared);
|
|
15758
16468
|
const report = {
|
|
15759
16469
|
cliVersion: resolveVersion(),
|
|
15760
16470
|
hostname: state.hostname,
|
|
@@ -15765,6 +16475,7 @@ async function runStatus(exec, asJson, notEnrolledHint = "conveyor-k3 setup") {
|
|
|
15765
16475
|
lastWarmAt: state.lastWarmAt,
|
|
15766
16476
|
lastPruneAt: state.lastPruneAt,
|
|
15767
16477
|
runningConveyorPods: await countRunningConveyorPods(exec),
|
|
16478
|
+
cloudflaredUpdates,
|
|
15768
16479
|
...state.runtime ? { runtime: state.runtime } : {}
|
|
15769
16480
|
};
|
|
15770
16481
|
if (asJson) {
|
|
@@ -15778,6 +16489,7 @@ async function runStatus(exec, asJson, notEnrolledHint = "conveyor-k3 setup") {
|
|
|
15778
16489
|
if (state.runtime) log(`runtime: ${state.runtime}`);
|
|
15779
16490
|
log(`k3s: ${report.k3sVersion ?? "not detected"}`);
|
|
15780
16491
|
for (const [unit, active] of Object.entries(units)) log(`${unit}: ${active}`);
|
|
16492
|
+
log(describeCloudflaredUpdater(cloudflaredUpdates));
|
|
15781
16493
|
log(`Settings version applied: ${report.appliedSettingsVersion}`);
|
|
15782
16494
|
log(`Conveyor pods running: ${report.runningConveyorPods}`);
|
|
15783
16495
|
}
|