@rallycry/conveyor-k3 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +865 -220
- package/dist/index.d.ts +31 -25
- package/dist/index.js +816 -193
- 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
|
|
|
@@ -4519,7 +4557,7 @@ 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
|
+
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, 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, 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, CARD_TYPE_SURFACE, surfaceTypes, BOARD_CARD_TYPES, REPORT_CARD_TYPES, DECISION_CARD_TYPES;
|
|
4523
4561
|
var init_dist = __esm({
|
|
4524
4562
|
"../shared/dist/index.js"() {
|
|
4525
4563
|
"use strict";
|
|
@@ -4538,6 +4576,7 @@ var init_dist = __esm({
|
|
|
4538
4576
|
init_zod();
|
|
4539
4577
|
init_zod();
|
|
4540
4578
|
init_zod();
|
|
4579
|
+
init_zod();
|
|
4541
4580
|
init_shared();
|
|
4542
4581
|
ACHIEVEMENT_RARITIES = [
|
|
4543
4582
|
{
|
|
@@ -4645,6 +4684,15 @@ var init_dist = __esm({
|
|
|
4645
4684
|
EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
|
|
4646
4685
|
EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
|
|
4647
4686
|
IDLE_HEARTBEAT_MS = 90 * 1e3;
|
|
4687
|
+
siteAssetExtension = external_exports.string().regex(/^[a-z0-9]{1,10}$/);
|
|
4688
|
+
RequestSiteAssetUploadRequestSchema = external_exports.object({
|
|
4689
|
+
projectId: external_exports.string(),
|
|
4690
|
+
siteId: external_exports.string().min(1).max(100),
|
|
4691
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
4692
|
+
ext: siteAssetExtension,
|
|
4693
|
+
sizeBytes: external_exports.number().int().positive()
|
|
4694
|
+
});
|
|
4695
|
+
ListSiteAssetTargetsRequestSchema = external_exports.object({ projectId: external_exports.string() });
|
|
4648
4696
|
TurnEndToolCallSchema = external_exports.object({
|
|
4649
4697
|
tool: external_exports.string(),
|
|
4650
4698
|
input: external_exports.string().optional(),
|
|
@@ -6532,6 +6580,8 @@ var init_dist = __esm({
|
|
|
6532
6580
|
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
6533
6581
|
anthropicEntry(FABLE_MODEL, "Fable 5.1", 10, 50)
|
|
6534
6582
|
];
|
|
6583
|
+
PROJECT_ARCHIVE_RETENTION_DAYS = 30;
|
|
6584
|
+
RETENTION_MS = PROJECT_ARCHIVE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
6535
6585
|
CLAUDESPACE_WORKLOAD_LABEL = "rc-workload";
|
|
6536
6586
|
CLAUDESPACE_WORKLOAD_VALUE = "claudespace";
|
|
6537
6587
|
CONVEYOR_POD_SELECTOR = `${CLAUDESPACE_WORKLOAD_LABEL}=${CLAUDESPACE_WORKLOAD_VALUE}`;
|
|
@@ -6556,6 +6606,8 @@ var init_dist = __esm({
|
|
|
6556
6606
|
PERSONAL_LOCAL_REGISTRY_PORT = 5e3;
|
|
6557
6607
|
PERSONAL_LOCAL_REGISTRY_HOST = `${PERSONAL_LOCAL_REGISTRY_NAME}.${PERSONAL_LOCAL_REGISTRY_NAMESPACE}.svc.cluster.local:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6558
6608
|
PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT = `http://127.0.0.1:${PERSONAL_LOCAL_REGISTRY_PORT}`;
|
|
6609
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET = "conveyor-registry-push";
|
|
6610
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY = "config.json";
|
|
6559
6611
|
BAKE_COMPONENT_LABEL = "claudespace.rallycry.com/component";
|
|
6560
6612
|
BAKE_COMPONENT_PREBAKE = "prebake";
|
|
6561
6613
|
BAKE_COMPONENT_STATE_BAKE = "state-bake";
|
|
@@ -7616,20 +7668,6 @@ function renderVmTemplate(arch, resources, dnsScript, firmware) {
|
|
|
7616
7668
|
provision: [{ mode: "system", script: dnsScript }]
|
|
7617
7669
|
});
|
|
7618
7670
|
}
|
|
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
7671
|
function validateStartupMemory(memoryGiB, hostBytes = totalmem(), others = [], relaySlots) {
|
|
7634
7672
|
const budgetGiB = memoryBudgetGiB(hostBytes) - reservedHostMemoryGiB(others);
|
|
7635
7673
|
if (memoryGiB > maximumGuestMemoryGiBWithin(budgetGiB, relaySlots)) {
|
|
@@ -7754,7 +7792,7 @@ var init_host_env = __esm({
|
|
|
7754
7792
|
});
|
|
7755
7793
|
|
|
7756
7794
|
// src/vm/endpoints.ts
|
|
7757
|
-
var CONTROL_PLANE_HOSTS, DEFAULT_ENDPOINTS,
|
|
7795
|
+
var CONTROL_PLANE_HOSTS, DEFAULT_ENDPOINTS, GUEST_CLI, TUNNEL_TCP;
|
|
7758
7796
|
var init_endpoints = __esm({
|
|
7759
7797
|
"src/vm/endpoints.ts"() {
|
|
7760
7798
|
"use strict";
|
|
@@ -7815,15 +7853,6 @@ var init_endpoints = __esm({
|
|
|
7815
7853
|
host,
|
|
7816
7854
|
ports: host.endsWith(".ubuntu.com") || host === "deb.debian.org" ? [80, 443] : [443]
|
|
7817
7855
|
}));
|
|
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
7856
|
GUEST_CLI = `set -euo pipefail
|
|
7828
7857
|
set -a
|
|
7829
7858
|
. /etc/conveyor/k3.env
|
|
@@ -8041,7 +8070,7 @@ var init_public_web = __esm({
|
|
|
8041
8070
|
// src/vm/relay.ts
|
|
8042
8071
|
import { kill } from "process";
|
|
8043
8072
|
import { execFileSync } from "child_process";
|
|
8044
|
-
import { lstatSync as lstatSync2, mkdirSync, readFileSync as
|
|
8073
|
+
import { lstatSync as lstatSync2, mkdirSync, readFileSync as readFileSync4, rmSync, writeFileSync } from "fs";
|
|
8045
8074
|
import { join as join3 } from "path";
|
|
8046
8075
|
import { Socket } from "net";
|
|
8047
8076
|
function validPid2(value) {
|
|
@@ -8082,7 +8111,7 @@ function stoppedSlotRecord(root, slot) {
|
|
|
8082
8111
|
}
|
|
8083
8112
|
try {
|
|
8084
8113
|
const record2 = JSON.parse(
|
|
8085
|
-
|
|
8114
|
+
readFileSync4(join3(path, "owner.json"), "utf8")
|
|
8086
8115
|
);
|
|
8087
8116
|
if (!validPid2(record2.pid)) throw new Error("missing process ID");
|
|
8088
8117
|
return record2;
|
|
@@ -8153,7 +8182,7 @@ var init_host_packages = __esm({
|
|
|
8153
8182
|
"firmware-x64": "ovmf",
|
|
8154
8183
|
"firmware-arm64": "qemu-efi-aarch64"
|
|
8155
8184
|
};
|
|
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
|
|
8185
|
+
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
8186
|
}
|
|
8158
8187
|
});
|
|
8159
8188
|
|
|
@@ -8248,13 +8277,25 @@ async function findFirmware(qemu, arch, host, os) {
|
|
|
8248
8277
|
const hint = packageHint(arch === "arm64" ? "firmware-arm64" : "firmware-x64", os);
|
|
8249
8278
|
throw new Error(`Install QEMU UEFI firmware before creating the VM${hint ? `: ${hint}` : "."}`);
|
|
8250
8279
|
}
|
|
8251
|
-
async function
|
|
8280
|
+
async function warnOnForeignPrefixOwner(exec, log2) {
|
|
8281
|
+
const result = await exec("/usr/bin/stat", ["-c", "%u %U %n", ...ROOT_OWNED_PREFIX]).catch(
|
|
8282
|
+
() => void 0
|
|
8283
|
+
);
|
|
8284
|
+
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}`})`);
|
|
8285
|
+
if (foreign.length === 0) return;
|
|
8286
|
+
log2(
|
|
8287
|
+
`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(" ")}`
|
|
8288
|
+
);
|
|
8289
|
+
}
|
|
8290
|
+
async function preflightHost(exec, paths, resources, host = realHost(), log2 = () => {
|
|
8291
|
+
}) {
|
|
8252
8292
|
if (!["darwin", "linux"].includes(host.platform) || host.uid === 0) {
|
|
8253
8293
|
throw new Error("Run the VM launcher as an ordinary user on macOS or Linux, without sudo.");
|
|
8254
8294
|
}
|
|
8255
8295
|
const arch = vmArch(host.arch);
|
|
8256
8296
|
const os = host.platform === "linux" ? await host.osRelease() : void 0;
|
|
8257
8297
|
if (host.platform === "linux") await assertKvm(host);
|
|
8298
|
+
if (host.platform === "linux") await warnOnForeignPrefixOwner(exec, log2);
|
|
8258
8299
|
const { lima, qemu, nc } = await locateTools(exec, paths, arch, host, os);
|
|
8259
8300
|
await assertLimaVersion(exec, paths, lima, host);
|
|
8260
8301
|
const acceleration = await runOrThrow(exec, qemu, ["-accel", "help"]);
|
|
@@ -8268,6 +8309,7 @@ async function preflightHost(exec, paths, resources, host = realHost()) {
|
|
|
8268
8309
|
const firmware = await findFirmware(qemu, arch, host, os);
|
|
8269
8310
|
return { arch, lima, qemu, nc, firmware };
|
|
8270
8311
|
}
|
|
8312
|
+
var ROOT_OWNED_PREFIX;
|
|
8271
8313
|
var init_preflight = __esm({
|
|
8272
8314
|
"src/vm/preflight.ts"() {
|
|
8273
8315
|
"use strict";
|
|
@@ -8275,16 +8317,17 @@ var init_preflight = __esm({
|
|
|
8275
8317
|
init_config();
|
|
8276
8318
|
init_host_env();
|
|
8277
8319
|
init_host_packages();
|
|
8320
|
+
ROOT_OWNED_PREFIX = ["/usr/local", "/usr/local/bin", "/usr/local/share"];
|
|
8278
8321
|
}
|
|
8279
8322
|
});
|
|
8280
8323
|
|
|
8281
8324
|
// src/vm/host-runtime.ts
|
|
8282
8325
|
import { readFile as readFile5 } from "fs/promises";
|
|
8283
|
-
import { createHash as
|
|
8326
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
8284
8327
|
import { join as join5 } from "path";
|
|
8285
8328
|
import { fileURLToPath } from "url";
|
|
8286
8329
|
function hashText(value) {
|
|
8287
|
-
return
|
|
8330
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
8288
8331
|
}
|
|
8289
8332
|
function newVmRunId() {
|
|
8290
8333
|
return randomUUID2();
|
|
@@ -8824,7 +8867,6 @@ __export(manager_exports, {
|
|
|
8824
8867
|
configureVm: () => configureVm,
|
|
8825
8868
|
createVm: () => createVm,
|
|
8826
8869
|
denyEndpoint: () => denyEndpoint,
|
|
8827
|
-
enrollVm: () => enrollVm,
|
|
8828
8870
|
enrollVmBootstrap: () => enrollVmBootstrap,
|
|
8829
8871
|
ensureVmDevicePublicKey: () => ensureVmDevicePublicKey,
|
|
8830
8872
|
internetMode: () => internetMode,
|
|
@@ -8850,16 +8892,15 @@ async function saveState(paths, state) {
|
|
|
8850
8892
|
await atomicPrivateWrite(paths.state, `${JSON.stringify(state, null, 2)}
|
|
8851
8893
|
`);
|
|
8852
8894
|
}
|
|
8853
|
-
async function createVm(ports, resources
|
|
8895
|
+
async function createVm(ports, resources) {
|
|
8854
8896
|
validateResources(resources, void 0, await ports.otherAllocations?.() ?? []);
|
|
8855
8897
|
const { paths, exec, log: log2 } = ports;
|
|
8856
|
-
const runtime = await preflightHost(exec, paths, resources);
|
|
8898
|
+
const runtime = await preflightHost(exec, paths, resources, void 0, log2);
|
|
8857
8899
|
const endpoints = [
|
|
8858
8900
|
...DEFAULT_ENDPOINTS,
|
|
8859
8901
|
{ host: "region1.v2.argotunnel.com", ports: [7844] },
|
|
8860
8902
|
{ host: "region2.v2.argotunnel.com", ports: [7844] }
|
|
8861
8903
|
];
|
|
8862
|
-
if (installUrl) endpoints.push({ host: parseInstallUrl(installUrl).hostname, ports: [443] });
|
|
8863
8904
|
const network = withHostSettings(await resolveNetwork(endpoints), {});
|
|
8864
8905
|
const template = renderVmTemplate(
|
|
8865
8906
|
runtime.arch,
|
|
@@ -8978,29 +9019,6 @@ async function beginEnrollment(ports, state) {
|
|
|
8978
9019
|
delete state.linkedDisplayName;
|
|
8979
9020
|
await saveState(ports.paths, state);
|
|
8980
9021
|
}
|
|
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
9022
|
async function enrollVmBootstrap(ports, state, bootstrapScript) {
|
|
9005
9023
|
if (bootstrapScript.length === 0 || bootstrapScript.length > 1e6)
|
|
9006
9024
|
throw new Error("Pairing bootstrap payload is invalid.");
|
|
@@ -9556,7 +9574,7 @@ done
|
|
|
9556
9574
|
});
|
|
9557
9575
|
|
|
9558
9576
|
// src/vm/dev-artifact.ts
|
|
9559
|
-
import { createHash as
|
|
9577
|
+
import { createHash as createHash5 } from "crypto";
|
|
9560
9578
|
import { readFile as readFile7, stat } from "fs/promises";
|
|
9561
9579
|
async function readDevArtifact() {
|
|
9562
9580
|
const file = process.env.CONVEYOR_DEV_ARTIFACT;
|
|
@@ -9570,7 +9588,7 @@ async function readDevArtifact() {
|
|
|
9570
9588
|
if (!info.isFile() || info.size > 32 * 1024 * 1024)
|
|
9571
9589
|
throw new Error("Development artifact must be a file of at most 32 MiB.");
|
|
9572
9590
|
const bytes = await readFile7(file);
|
|
9573
|
-
if (bytes.length > 32 * 1024 * 1024 ||
|
|
9591
|
+
if (bytes.length > 32 * 1024 * 1024 || createHash5("sha256").update(bytes).digest("hex") !== digest)
|
|
9574
9592
|
throw new Error("Development artifact checksum mismatch.");
|
|
9575
9593
|
return { bytes, sha256: digest };
|
|
9576
9594
|
}
|
|
@@ -10665,14 +10683,6 @@ function commandPorts(exec, instance, log2, layout) {
|
|
|
10665
10683
|
otherAllocations: async () => (await readOtherAllocations(paths, layout)).map((other) => other.resources)
|
|
10666
10684
|
};
|
|
10667
10685
|
}
|
|
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
10686
|
function resourceFlag(flags, key, fallback) {
|
|
10677
10687
|
if (flags[key] === void 0) return fallback;
|
|
10678
10688
|
if (typeof flags[key] !== "string" || !/^\d+$/.test(flags[key]))
|
|
@@ -10699,7 +10709,7 @@ function setupDefaults(others, host = hostCapacity()) {
|
|
|
10699
10709
|
diskGiB: 64
|
|
10700
10710
|
};
|
|
10701
10711
|
}
|
|
10702
|
-
async function createInstance(ports, parsed
|
|
10712
|
+
async function createInstance(ports, parsed) {
|
|
10703
10713
|
if (ports.paths.instance !== DEFAULT_INSTANCE) assertInstancePathFits(ports.paths);
|
|
10704
10714
|
return await withAdmissionLock(async (release) => {
|
|
10705
10715
|
const defaults = setupDefaults(await readOtherAllocations(ports.paths, ports.layout));
|
|
@@ -10709,8 +10719,7 @@ async function createInstance(ports, parsed, url) {
|
|
|
10709
10719
|
cpus: resourceFlag(parsed.flags, "cpus", defaults.cpus),
|
|
10710
10720
|
memoryGiB: resourceFlag(parsed.flags, "memory", defaults.memoryGiB),
|
|
10711
10721
|
diskGiB: resourceFlag(parsed.flags, "disk", defaults.diskGiB)
|
|
10712
|
-
}
|
|
10713
|
-
url
|
|
10722
|
+
}
|
|
10714
10723
|
);
|
|
10715
10724
|
}, ports.layout.base);
|
|
10716
10725
|
}
|
|
@@ -10745,17 +10754,12 @@ function relinkNotice(state, relink) {
|
|
|
10745
10754
|
);
|
|
10746
10755
|
}
|
|
10747
10756
|
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
10757
|
if (state && RESOURCE_FLAGS.some((key) => parsed.flags[key] !== void 0)) {
|
|
10751
10758
|
throw new Error("This VM already exists; setup resource flags only apply at creation.");
|
|
10752
10759
|
}
|
|
10753
|
-
|
|
10754
|
-
if (notice) ports.log(notice);
|
|
10755
|
-
if (!state) state = await createInstance(ports, parsed, url);
|
|
10760
|
+
if (!state) state = await createInstance(ports, parsed);
|
|
10756
10761
|
await startVm(ports, state);
|
|
10757
|
-
|
|
10758
|
-
else ports.log(setupNextStep(state, ports.paths.instance));
|
|
10762
|
+
ports.log(setupNextStep(state, ports.paths.instance));
|
|
10759
10763
|
}
|
|
10760
10764
|
function setupNextStep(state, instance) {
|
|
10761
10765
|
const link = instance === DEFAULT_INSTANCE ? "conveyor-k3 vm link" : `conveyor-k3 vm link --instance=${instance}`;
|
|
@@ -10932,7 +10936,7 @@ var init_service_unit = __esm({
|
|
|
10932
10936
|
});
|
|
10933
10937
|
|
|
10934
10938
|
// src/vm/service-control.ts
|
|
10935
|
-
import { constants as constants2, readFileSync as
|
|
10939
|
+
import { constants as constants2, readFileSync as readFileSync5 } from "fs";
|
|
10936
10940
|
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
10941
|
import { tmpdir as tmpdir2, userInfo as userInfo3 } from "os";
|
|
10938
10942
|
import { dirname as dirname3, join as join12 } from "path";
|
|
@@ -10966,7 +10970,7 @@ function realServiceHost(exec, log2) {
|
|
|
10966
10970
|
function cliVersion() {
|
|
10967
10971
|
try {
|
|
10968
10972
|
const url = new URL("../package.json", import.meta.url);
|
|
10969
|
-
return JSON.parse(
|
|
10973
|
+
return JSON.parse(readFileSync5(url, "utf8")).version ?? "<version>";
|
|
10970
10974
|
} catch {
|
|
10971
10975
|
return "<version>";
|
|
10972
10976
|
}
|
|
@@ -11616,7 +11620,7 @@ function validateFlags(parsed) {
|
|
|
11616
11620
|
const action = actionOf(parsed);
|
|
11617
11621
|
const allowed = {
|
|
11618
11622
|
...BAKE_VERBS,
|
|
11619
|
-
setup: ["
|
|
11623
|
+
setup: ["cpus", "memory", "disk"],
|
|
11620
11624
|
start: [],
|
|
11621
11625
|
configure: [...RESOURCE_FLAGS2, ...CAPACITY_FLAGS],
|
|
11622
11626
|
stop: ["force"],
|
|
@@ -11640,7 +11644,7 @@ function validateFlags(parsed) {
|
|
|
11640
11644
|
for (const flag of Object.keys(parsed.flags)) {
|
|
11641
11645
|
if (!accepted.includes(flag)) throw new Error(`Unknown flag for vm ${action}: --${flag}`);
|
|
11642
11646
|
}
|
|
11643
|
-
const booleanFlags = /* @__PURE__ */ new Set(["
|
|
11647
|
+
const booleanFlags = /* @__PURE__ */ new Set(["force", "json", "purge", "yes", "relink"]);
|
|
11644
11648
|
for (const [flag, value] of Object.entries(parsed.flags)) {
|
|
11645
11649
|
if (booleanFlags.has(flag) && value !== true) throw new Error(`Use --${flag} without a value.`);
|
|
11646
11650
|
}
|
|
@@ -11761,7 +11765,7 @@ var init_command = __esm({
|
|
|
11761
11765
|
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
11766
|
Every command but list acts on one VM: --instance=<name> selects a named VM,
|
|
11763
11767
|
and omitting it selects this user's default VM.
|
|
11764
|
-
setup [--
|
|
11768
|
+
setup [--cpus=4] [--memory=8] [--disk=64]
|
|
11765
11769
|
configure --cpus=4 --memory=8 [--disk=64] Changes allocation while stopped.
|
|
11766
11770
|
configure --profile=default|server [--relay-slots=96] [--relay-max-gib=8]
|
|
11767
11771
|
[--disk-mbps=1000] [--disk-iops=20000] Changes network and disk capacity while stopped.
|
|
@@ -11795,6 +11799,7 @@ Run without sudo. Requires Lima 2.2+ and QEMU with HVF (macOS) or KVM (Linux).
|
|
|
11795
11799
|
var command_exports2 = {};
|
|
11796
11800
|
__export(command_exports2, {
|
|
11797
11801
|
HOST_HELP: () => HOST_HELP,
|
|
11802
|
+
hostNextStep: () => hostNextStep,
|
|
11798
11803
|
runHostCommand: () => runHostCommand
|
|
11799
11804
|
});
|
|
11800
11805
|
import { hostname } from "os";
|
|
@@ -11848,6 +11853,11 @@ async function prepareHostLink(parsed, ports, log2) {
|
|
|
11848
11853
|
}
|
|
11849
11854
|
return { api, name: name2 };
|
|
11850
11855
|
}
|
|
11856
|
+
function hostNextStep(state) {
|
|
11857
|
+
if (state.k3sInstalledByConveyor === true)
|
|
11858
|
+
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.";
|
|
11859
|
+
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.";
|
|
11860
|
+
}
|
|
11851
11861
|
async function completeHostLink(api, pairing, log2, ports) {
|
|
11852
11862
|
const poll = () => pollPairing(api, pairing.deviceCode);
|
|
11853
11863
|
const retry = () => log2("Securing connection\u2026 retrying pairing status.");
|
|
@@ -11880,6 +11890,7 @@ async function completeHostLink(api, pairing, log2, ports) {
|
|
|
11880
11890
|
log2(
|
|
11881
11891
|
`Linked to ${completed.accountLabel ?? "Conveyor"}${completed.displayName ? ` as ${completed.displayName}` : ""}.`
|
|
11882
11892
|
);
|
|
11893
|
+
log2(hostNextStep(await ports.readState()));
|
|
11883
11894
|
}
|
|
11884
11895
|
async function hostLink(parsed, log2, ports) {
|
|
11885
11896
|
const { api, name: name2 } = await prepareHostLink(parsed, ports, log2);
|
|
@@ -11944,8 +11955,10 @@ Usage: conveyor-k3 host <setup|link|status|uninstall>
|
|
|
11944
11955
|
uninstall [--purge-k3s --yes] Remove Conveyor's units, state, and device key. k3s,
|
|
11945
11956
|
your config, and your workloads stay unless you purge.
|
|
11946
11957
|
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
|
-
|
|
11958
|
+
no VM boundary; Pod Security and an egress policy stand in for one. After link, on a
|
|
11959
|
+
k3s you installed yourself: set Keep for this machine in Conveyor and Save, then run
|
|
11960
|
+
sudo systemctl restart k3s once. Conveyor never restarts a k3s it did not install.
|
|
11961
|
+
See the guide's "Link a dedicated server (host)" section.
|
|
11949
11962
|
`;
|
|
11950
11963
|
ALLOWED_FLAGS = /* @__PURE__ */ new Map([
|
|
11951
11964
|
["setup", []],
|
|
@@ -12026,7 +12039,7 @@ async function writeWorkloads(data) {
|
|
|
12026
12039
|
|
|
12027
12040
|
// src/cli.ts
|
|
12028
12041
|
init_exec();
|
|
12029
|
-
import { readFileSync as
|
|
12042
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
12030
12043
|
|
|
12031
12044
|
// src/lib/fs-port.ts
|
|
12032
12045
|
import { chmod, mkdir as mkdir2, readFile, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -12040,7 +12053,7 @@ var nodeFileSystem = {
|
|
|
12040
12053
|
},
|
|
12041
12054
|
async writeFile(path, content, mode) {
|
|
12042
12055
|
await mkdir2(path.slice(0, path.lastIndexOf("/")), { recursive: true });
|
|
12043
|
-
await writeFile2(path, content, "utf8");
|
|
12056
|
+
await writeFile2(path, content, { encoding: "utf8", ...mode === void 0 ? {} : { mode } });
|
|
12044
12057
|
if (mode !== void 0) await chmod(path, mode);
|
|
12045
12058
|
},
|
|
12046
12059
|
async removeFile(path) {
|
|
@@ -12091,11 +12104,235 @@ init_dist();
|
|
|
12091
12104
|
// src/lib/local-registry.ts
|
|
12092
12105
|
init_dist();
|
|
12093
12106
|
init_paths();
|
|
12107
|
+
import { createHash as createHash2 } from "crypto";
|
|
12108
|
+
|
|
12109
|
+
// src/lib/registry-credentials.ts
|
|
12110
|
+
import { createHash, randomBytes } from "crypto";
|
|
12111
|
+
|
|
12112
|
+
// src/lib/bcrypt.ts
|
|
12113
|
+
var P_WORDS = 18;
|
|
12114
|
+
var S_WORDS = 1024;
|
|
12115
|
+
var MAGIC = "OrpheanBeholderScryDoubt";
|
|
12116
|
+
var ALPHABET = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
12117
|
+
var MAX_KEY_BYTES = 72;
|
|
12118
|
+
var BCRYPT_SALT_BYTES = 16;
|
|
12119
|
+
function scaledAtanInverse(x, one) {
|
|
12120
|
+
const xSquared = x * x;
|
|
12121
|
+
let power = one / x;
|
|
12122
|
+
let sum = 0n;
|
|
12123
|
+
for (let n = 0n; power !== 0n; n++) {
|
|
12124
|
+
const term = power / (2n * n + 1n);
|
|
12125
|
+
sum = n % 2n === 0n ? sum + term : sum - term;
|
|
12126
|
+
power /= xSquared;
|
|
12127
|
+
}
|
|
12128
|
+
return sum;
|
|
12129
|
+
}
|
|
12130
|
+
function piFractionWords(count) {
|
|
12131
|
+
const guard = 64n;
|
|
12132
|
+
const bits = BigInt(count * 32) + guard;
|
|
12133
|
+
const one = 1n << bits;
|
|
12134
|
+
const pi = 16n * scaledAtanInverse(5n, one) - 4n * scaledAtanInverse(239n, one);
|
|
12135
|
+
const fraction = pi - 3n * one >> guard;
|
|
12136
|
+
const words = new Uint32Array(count);
|
|
12137
|
+
for (let index = 0; index < count; index++) {
|
|
12138
|
+
words[index] = Number(fraction >> BigInt(32 * (count - 1 - index)) & 0xffffffffn);
|
|
12139
|
+
}
|
|
12140
|
+
return words;
|
|
12141
|
+
}
|
|
12142
|
+
var initialState = null;
|
|
12143
|
+
function blowfishInitialState() {
|
|
12144
|
+
if (!initialState) {
|
|
12145
|
+
const words = piFractionWords(P_WORDS + S_WORDS);
|
|
12146
|
+
initialState = { p: words.slice(0, P_WORDS), s: words.slice(P_WORDS) };
|
|
12147
|
+
}
|
|
12148
|
+
return initialState;
|
|
12149
|
+
}
|
|
12150
|
+
function word(array, index) {
|
|
12151
|
+
return array[index] ?? 0;
|
|
12152
|
+
}
|
|
12153
|
+
function round(s, x) {
|
|
12154
|
+
const mixed = word(s, x >>> 24) + word(s, 256 + (x >>> 16 & 255)) ^ word(s, 512 + (x >>> 8 & 255));
|
|
12155
|
+
return mixed + word(s, 768 + (x & 255)) | 0;
|
|
12156
|
+
}
|
|
12157
|
+
function encipher(state, data, offset) {
|
|
12158
|
+
const { p, s } = state;
|
|
12159
|
+
let left = word(data, offset) ^ word(p, 0);
|
|
12160
|
+
let right = word(data, offset + 1);
|
|
12161
|
+
for (let index = 1; index <= 16; index += 2) {
|
|
12162
|
+
right ^= round(s, left) ^ word(p, index);
|
|
12163
|
+
left ^= round(s, right) ^ word(p, index + 1);
|
|
12164
|
+
}
|
|
12165
|
+
data[offset] = right ^ word(p, 17);
|
|
12166
|
+
data[offset + 1] = left;
|
|
12167
|
+
}
|
|
12168
|
+
function wordStream(bytes) {
|
|
12169
|
+
let position = 0;
|
|
12170
|
+
return () => {
|
|
12171
|
+
let value = 0;
|
|
12172
|
+
for (let index = 0; index < 4; index++) {
|
|
12173
|
+
value = value << 8 | (bytes[position] ?? 0);
|
|
12174
|
+
position = (position + 1) % bytes.length;
|
|
12175
|
+
}
|
|
12176
|
+
return value >>> 0;
|
|
12177
|
+
};
|
|
12178
|
+
}
|
|
12179
|
+
function expandKey(state, key, salt) {
|
|
12180
|
+
const nextKeyWord = wordStream(key);
|
|
12181
|
+
for (let index = 0; index < P_WORDS; index++) {
|
|
12182
|
+
state.p[index] = word(state.p, index) ^ nextKeyWord();
|
|
12183
|
+
}
|
|
12184
|
+
const nextSaltWord = salt ? wordStream(salt) : () => 0;
|
|
12185
|
+
const block = new Uint32Array(2);
|
|
12186
|
+
const fill = (target2, length) => {
|
|
12187
|
+
for (let index = 0; index < length; index += 2) {
|
|
12188
|
+
block[0] = word(block, 0) ^ nextSaltWord();
|
|
12189
|
+
block[1] = word(block, 1) ^ nextSaltWord();
|
|
12190
|
+
encipher(state, block, 0);
|
|
12191
|
+
target2[index] = word(block, 0);
|
|
12192
|
+
target2[index + 1] = word(block, 1);
|
|
12193
|
+
}
|
|
12194
|
+
};
|
|
12195
|
+
fill(state.p, P_WORDS);
|
|
12196
|
+
fill(state.s, S_WORDS);
|
|
12197
|
+
}
|
|
12198
|
+
function encodeBase64(bytes, length) {
|
|
12199
|
+
let out = "";
|
|
12200
|
+
let offset = 0;
|
|
12201
|
+
while (offset < length) {
|
|
12202
|
+
let first = bytes[offset++] ?? 0;
|
|
12203
|
+
out += ALPHABET[first >> 2];
|
|
12204
|
+
first = (first & 3) << 4;
|
|
12205
|
+
if (offset >= length) {
|
|
12206
|
+
out += ALPHABET[first];
|
|
12207
|
+
break;
|
|
12208
|
+
}
|
|
12209
|
+
let second = bytes[offset++] ?? 0;
|
|
12210
|
+
out += ALPHABET[first | second >> 4];
|
|
12211
|
+
first = (second & 15) << 2;
|
|
12212
|
+
if (offset >= length) {
|
|
12213
|
+
out += ALPHABET[first];
|
|
12214
|
+
break;
|
|
12215
|
+
}
|
|
12216
|
+
second = bytes[offset++] ?? 0;
|
|
12217
|
+
out += ALPHABET[first | second >> 6];
|
|
12218
|
+
out += ALPHABET[second & 63];
|
|
12219
|
+
}
|
|
12220
|
+
return out;
|
|
12221
|
+
}
|
|
12222
|
+
function bcryptHash(password, cost, salt) {
|
|
12223
|
+
if (!Number.isInteger(cost) || cost < 4 || cost > 31) {
|
|
12224
|
+
throw new Error(`bcrypt cost must be an integer from 4 to 31, not ${cost}`);
|
|
12225
|
+
}
|
|
12226
|
+
if (salt.length !== BCRYPT_SALT_BYTES) {
|
|
12227
|
+
throw new Error(`bcrypt needs a ${BCRYPT_SALT_BYTES}-byte salt`);
|
|
12228
|
+
}
|
|
12229
|
+
const passwordBytes = new TextEncoder().encode(password).slice(0, MAX_KEY_BYTES);
|
|
12230
|
+
const key = new Uint8Array(passwordBytes.length + 1);
|
|
12231
|
+
key.set(passwordBytes);
|
|
12232
|
+
const base = blowfishInitialState();
|
|
12233
|
+
const state = { p: base.p.slice(), s: base.s.slice() };
|
|
12234
|
+
expandKey(state, key, salt);
|
|
12235
|
+
for (let rounds = 2 ** cost; rounds > 0; rounds--) {
|
|
12236
|
+
expandKey(state, key, null);
|
|
12237
|
+
expandKey(state, salt, null);
|
|
12238
|
+
}
|
|
12239
|
+
const magic = new TextEncoder().encode(MAGIC);
|
|
12240
|
+
const nextMagicWord = wordStream(magic);
|
|
12241
|
+
const block = Uint32Array.from({ length: 6 }, () => nextMagicWord());
|
|
12242
|
+
for (let pass = 0; pass < 64; pass++) {
|
|
12243
|
+
for (let offset = 0; offset < 6; offset += 2) encipher(state, block, offset);
|
|
12244
|
+
}
|
|
12245
|
+
const digest = new Uint8Array(24);
|
|
12246
|
+
block.forEach((value, index) => {
|
|
12247
|
+
digest[index * 4] = value >>> 24;
|
|
12248
|
+
digest[index * 4 + 1] = value >>> 16 & 255;
|
|
12249
|
+
digest[index * 4 + 2] = value >>> 8 & 255;
|
|
12250
|
+
digest[index * 4 + 3] = value & 255;
|
|
12251
|
+
});
|
|
12252
|
+
const costText = String(cost).padStart(2, "0");
|
|
12253
|
+
return `$2y$${costText}$${encodeBase64(salt, BCRYPT_SALT_BYTES)}${encodeBase64(digest, 23)}`;
|
|
12254
|
+
}
|
|
12255
|
+
|
|
12256
|
+
// src/lib/registry-credentials.ts
|
|
12257
|
+
init_paths();
|
|
12258
|
+
var LOCAL_REGISTRY_USERNAME = "conveyor";
|
|
12259
|
+
var BCRYPT_COST = 5;
|
|
12260
|
+
var USERNAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
12261
|
+
var PASSWORD = /^[A-Za-z0-9_-]{32,128}$/;
|
|
12262
|
+
function generateRegistryCredentials(random = randomBytes) {
|
|
12263
|
+
const username = LOCAL_REGISTRY_USERNAME;
|
|
12264
|
+
const password = Buffer.from(random(32)).toString("base64url");
|
|
12265
|
+
const hash = bcryptHash(password, BCRYPT_COST, random(BCRYPT_SALT_BYTES));
|
|
12266
|
+
return { username, password, htpasswd: `${username}:${hash}` };
|
|
12267
|
+
}
|
|
12268
|
+
function parseRegistryCredentials(raw) {
|
|
12269
|
+
if (!raw) return null;
|
|
12270
|
+
try {
|
|
12271
|
+
const value = JSON.parse(raw);
|
|
12272
|
+
const { username, password, htpasswd } = value ?? {};
|
|
12273
|
+
if (typeof username !== "string" || !USERNAME.test(username)) return null;
|
|
12274
|
+
if (typeof password !== "string" || !PASSWORD.test(password)) return null;
|
|
12275
|
+
if (typeof htpasswd !== "string" || !htpasswd.startsWith(`${username}:$2`)) return null;
|
|
12276
|
+
if (htpasswd.includes("\n")) return null;
|
|
12277
|
+
return { username, password, htpasswd };
|
|
12278
|
+
} catch {
|
|
12279
|
+
return null;
|
|
12280
|
+
}
|
|
12281
|
+
}
|
|
12282
|
+
async function loadOrCreateRegistryCredentials(fs2, random) {
|
|
12283
|
+
const stored = parseRegistryCredentials(await fs2.readFile(LOCAL_REGISTRY_CREDENTIALS_FILE));
|
|
12284
|
+
if (stored) return stored;
|
|
12285
|
+
const created = generateRegistryCredentials(random);
|
|
12286
|
+
await fs2.writeFile(LOCAL_REGISTRY_CREDENTIALS_FILE, `${JSON.stringify(created)}
|
|
12287
|
+
`, 384);
|
|
12288
|
+
return created;
|
|
12289
|
+
}
|
|
12290
|
+
function credentialFingerprint(credentials) {
|
|
12291
|
+
return createHash("sha256").update(`${credentials.username}:${credentials.password}`).digest("hex").slice(0, 16);
|
|
12292
|
+
}
|
|
12293
|
+
function basicAuthorization(credentials) {
|
|
12294
|
+
return `Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64")}`;
|
|
12295
|
+
}
|
|
12296
|
+
function registryDockerConfig(credentials, host) {
|
|
12297
|
+
const auth = Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64");
|
|
12298
|
+
return JSON.stringify({ auths: { [host]: { auth } } });
|
|
12299
|
+
}
|
|
12300
|
+
function parseRegistryAuthState(raw) {
|
|
12301
|
+
if (!raw) return null;
|
|
12302
|
+
try {
|
|
12303
|
+
const value = JSON.parse(raw);
|
|
12304
|
+
if (typeof value?.credential !== "string") return null;
|
|
12305
|
+
const writtenAt = typeof value.writtenAt === "string" && !Number.isNaN(Date.parse(value.writtenAt)) ? value.writtenAt : null;
|
|
12306
|
+
return { credential: value.credential, writtenAt, enforced: value.enforced === true };
|
|
12307
|
+
} catch {
|
|
12308
|
+
return null;
|
|
12309
|
+
}
|
|
12310
|
+
}
|
|
12311
|
+
async function registryAuthEnforced(readFile9) {
|
|
12312
|
+
return parseRegistryAuthState(await readFile9(LOCAL_REGISTRY_AUTH_STATE_FILE))?.enforced === true;
|
|
12313
|
+
}
|
|
12314
|
+
function withRegistryCredentials(fetchImpl, readFile9) {
|
|
12315
|
+
let credentials = null;
|
|
12316
|
+
return async (url, init) => {
|
|
12317
|
+
credentials ??= readFile9(LOCAL_REGISTRY_CREDENTIALS_FILE).then(parseRegistryCredentials);
|
|
12318
|
+
const stored = await credentials;
|
|
12319
|
+
if (!stored) return fetchImpl(url, init);
|
|
12320
|
+
const headers = new Headers(init?.headers);
|
|
12321
|
+
headers.set("Authorization", basicAuthorization(stored));
|
|
12322
|
+
return fetchImpl(url, { ...init, headers });
|
|
12323
|
+
};
|
|
12324
|
+
}
|
|
12325
|
+
|
|
12326
|
+
// src/lib/local-registry.ts
|
|
12094
12327
|
var LOCAL_REGISTRY_IMAGE = "docker.io/library/registry:3.1.2@sha256:c87f33837722a100572e95d7dc4bf539fc42cf68202b13c3bc03c0ff54c3a649";
|
|
12095
12328
|
var LOCAL_REGISTRY_CONFIG = "/etc/distribution/config.yml";
|
|
12096
12329
|
var LOCAL_REGISTRY_GC_JOB = "conveyor-registry-gc";
|
|
12330
|
+
var LOCAL_REGISTRY_HTPASSWD_SECRET = "conveyor-registry-htpasswd";
|
|
12331
|
+
var HTPASSWD_KEY = "htpasswd";
|
|
12332
|
+
var AUTH_MOUNT = "/etc/distribution/auth";
|
|
12097
12333
|
var LOCAL_REGISTRY_SELECTOR = { "app.kubernetes.io/name": PERSONAL_LOCAL_REGISTRY_NAME };
|
|
12098
12334
|
var MANAGED_BY = { "app.kubernetes.io/managed-by": "conveyor-k3" };
|
|
12335
|
+
var AUTH_CHECKSUM_ANNOTATION = "conveyor.rallycry.com/registry-auth";
|
|
12099
12336
|
var DATA_MOUNT = "/var/lib/registry";
|
|
12100
12337
|
var REGISTRY_ENV = [
|
|
12101
12338
|
{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" },
|
|
@@ -12110,6 +12347,23 @@ var VOLUME_MOUNTS = [
|
|
|
12110
12347
|
{ name: "data", mountPath: DATA_MOUNT },
|
|
12111
12348
|
{ name: "tmp", mountPath: "/tmp" }
|
|
12112
12349
|
];
|
|
12350
|
+
var AUTH_ENV = [
|
|
12351
|
+
{ name: "REGISTRY_AUTH", value: "htpasswd" },
|
|
12352
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_REALM", value: "conveyor" },
|
|
12353
|
+
{ name: "REGISTRY_AUTH_HTPASSWD_PATH", value: `${AUTH_MOUNT}/${HTPASSWD_KEY}` }
|
|
12354
|
+
];
|
|
12355
|
+
var AUTH_VOLUME = {
|
|
12356
|
+
name: "auth",
|
|
12357
|
+
secret: {
|
|
12358
|
+
secretName: LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
12359
|
+
items: [{ key: HTPASSWD_KEY, path: HTPASSWD_KEY }],
|
|
12360
|
+
defaultMode: 256
|
|
12361
|
+
}
|
|
12362
|
+
};
|
|
12363
|
+
var AUTH_VOLUME_MOUNT = { name: "auth", mountPath: AUTH_MOUNT, readOnly: true };
|
|
12364
|
+
function htpasswdChecksum(htpasswd) {
|
|
12365
|
+
return createHash2("sha256").update(htpasswd).digest("hex").slice(0, 16);
|
|
12366
|
+
}
|
|
12113
12367
|
var VOLUMES = [
|
|
12114
12368
|
{ name: "data", hostPath: { path: LOCAL_REGISTRY_DATA_DIR, type: "Directory" } },
|
|
12115
12369
|
{ name: "tmp", emptyDir: { sizeLimit: "64Mi" } }
|
|
@@ -12146,7 +12400,34 @@ function registryNetworkPolicy(metadata) {
|
|
|
12146
12400
|
}
|
|
12147
12401
|
};
|
|
12148
12402
|
}
|
|
12149
|
-
function
|
|
12403
|
+
function renderLocalRegistrySecrets(credentials) {
|
|
12404
|
+
const encode = (value) => Buffer.from(value).toString("base64");
|
|
12405
|
+
const secret = (namespace, name2, key, value) => ({
|
|
12406
|
+
apiVersion: "v1",
|
|
12407
|
+
kind: "Secret",
|
|
12408
|
+
metadata: { name: name2, namespace, labels: { ...MANAGED_BY } },
|
|
12409
|
+
type: "Opaque",
|
|
12410
|
+
data: { [key]: encode(value) }
|
|
12411
|
+
});
|
|
12412
|
+
const items = [
|
|
12413
|
+
secret(
|
|
12414
|
+
PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
12415
|
+
LOCAL_REGISTRY_HTPASSWD_SECRET,
|
|
12416
|
+
HTPASSWD_KEY,
|
|
12417
|
+
`${credentials.htpasswd}
|
|
12418
|
+
`
|
|
12419
|
+
),
|
|
12420
|
+
secret(
|
|
12421
|
+
PERSONAL_BAKE_NAMESPACE,
|
|
12422
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET,
|
|
12423
|
+
PERSONAL_LOCAL_REGISTRY_PUSH_SECRET_KEY,
|
|
12424
|
+
registryDockerConfig(credentials, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
12425
|
+
)
|
|
12426
|
+
];
|
|
12427
|
+
return `${JSON.stringify({ apiVersion: "v1", kind: "List", items }, null, 2)}
|
|
12428
|
+
`;
|
|
12429
|
+
}
|
|
12430
|
+
function renderLocalRegistryManifests(auth = null) {
|
|
12150
12431
|
const metadata = {
|
|
12151
12432
|
name: PERSONAL_LOCAL_REGISTRY_NAME,
|
|
12152
12433
|
namespace: PERSONAL_LOCAL_REGISTRY_NAMESPACE,
|
|
@@ -12162,15 +12443,19 @@ function renderLocalRegistryManifests() {
|
|
|
12162
12443
|
strategy: { type: "Recreate" },
|
|
12163
12444
|
selector: { matchLabels: LOCAL_REGISTRY_SELECTOR },
|
|
12164
12445
|
template: {
|
|
12165
|
-
metadata: {
|
|
12446
|
+
metadata: {
|
|
12447
|
+
labels: metadata.labels,
|
|
12448
|
+
...auth ? { annotations: { [AUTH_CHECKSUM_ANNOTATION]: htpasswdChecksum(auth.htpasswd) } } : {}
|
|
12449
|
+
},
|
|
12166
12450
|
spec: {
|
|
12167
12451
|
...POD_BASE,
|
|
12452
|
+
...auth ? { volumes: [...VOLUMES, AUTH_VOLUME] } : {},
|
|
12168
12453
|
containers: [
|
|
12169
12454
|
{
|
|
12170
12455
|
name: "registry",
|
|
12171
12456
|
image: LOCAL_REGISTRY_IMAGE,
|
|
12172
12457
|
imagePullPolicy: "IfNotPresent",
|
|
12173
|
-
env: REGISTRY_ENV,
|
|
12458
|
+
env: auth ? [...REGISTRY_ENV, ...AUTH_ENV] : REGISTRY_ENV,
|
|
12174
12459
|
ports: [
|
|
12175
12460
|
{
|
|
12176
12461
|
name: "registry",
|
|
@@ -12182,8 +12467,10 @@ function renderLocalRegistryManifests() {
|
|
|
12182
12467
|
protocol: "TCP"
|
|
12183
12468
|
}
|
|
12184
12469
|
],
|
|
12470
|
+
// A TCP check: once the credential is enforced, `/v2/` answers
|
|
12471
|
+
// 401 to a probe that does not carry it.
|
|
12185
12472
|
readinessProbe: {
|
|
12186
|
-
|
|
12473
|
+
tcpSocket: { port: PERSONAL_LOCAL_REGISTRY_PORT },
|
|
12187
12474
|
periodSeconds: 10
|
|
12188
12475
|
},
|
|
12189
12476
|
resources: {
|
|
@@ -12191,7 +12478,7 @@ function renderLocalRegistryManifests() {
|
|
|
12191
12478
|
limits: { memory: "512Mi" }
|
|
12192
12479
|
},
|
|
12193
12480
|
securityContext: CONTAINER_SECURITY,
|
|
12194
|
-
volumeMounts: VOLUME_MOUNTS
|
|
12481
|
+
volumeMounts: auth ? [...VOLUME_MOUNTS, AUTH_VOLUME_MOUNT] : VOLUME_MOUNTS
|
|
12195
12482
|
}
|
|
12196
12483
|
]
|
|
12197
12484
|
}
|
|
@@ -12282,11 +12569,18 @@ async function registryAnswers(fetchImpl = fetch) {
|
|
|
12282
12569
|
}
|
|
12283
12570
|
async function localRegistryReady(ports) {
|
|
12284
12571
|
const [answers, mirrored] = await Promise.all([
|
|
12285
|
-
registryAnswers(ports.fetch),
|
|
12572
|
+
registryAnswers(withRegistryCredentials(ports.fetch ?? fetch, ports.readFile)),
|
|
12286
12573
|
mirrorLoaded(ports.readFile)
|
|
12287
12574
|
]);
|
|
12288
12575
|
return answers && mirrored;
|
|
12289
12576
|
}
|
|
12577
|
+
async function localRegistryStatus(ports) {
|
|
12578
|
+
const [ready, authEnforced] = await Promise.all([
|
|
12579
|
+
localRegistryReady(ports),
|
|
12580
|
+
registryAuthEnforced(ports.readFile)
|
|
12581
|
+
]);
|
|
12582
|
+
return { ready, authEnforced };
|
|
12583
|
+
}
|
|
12290
12584
|
|
|
12291
12585
|
// src/daemon/registry-gc.ts
|
|
12292
12586
|
init_paths();
|
|
@@ -12683,7 +12977,10 @@ async function collectRegistryGarbage(ports, options) {
|
|
|
12683
12977
|
blobsCollected: false
|
|
12684
12978
|
});
|
|
12685
12979
|
const now = ports.now ?? Date.now;
|
|
12686
|
-
const fetchImpl =
|
|
12980
|
+
const fetchImpl = withRegistryCredentials(
|
|
12981
|
+
ports.fetch ?? fetch,
|
|
12982
|
+
ports.readFile ?? ((path) => nodeFileSystem.readFile(path))
|
|
12983
|
+
);
|
|
12687
12984
|
const retain = await readRetainList(ports);
|
|
12688
12985
|
if (typeof retain === "string") return skip(retain);
|
|
12689
12986
|
const blocked = await registryBlocker(ports, now());
|
|
@@ -12750,6 +13047,16 @@ function upsertRegistryAuth(file, host, auth) {
|
|
|
12750
13047
|
next.configs = configs;
|
|
12751
13048
|
return next;
|
|
12752
13049
|
}
|
|
13050
|
+
function removeRegistryAuth(file, host) {
|
|
13051
|
+
const existing = file.configs?.[host];
|
|
13052
|
+
if (!existing?.auth) return file;
|
|
13053
|
+
const { auth: _auth, ...rest } = existing;
|
|
13054
|
+
const { [host]: _removed, ...others } = file.configs ?? {};
|
|
13055
|
+
const configs = Object.keys(rest).length > 0 ? { ...others, [host]: rest } : others;
|
|
13056
|
+
const next = { ...file, configs };
|
|
13057
|
+
if (Object.keys(configs).length === 0) delete next.configs;
|
|
13058
|
+
return next;
|
|
13059
|
+
}
|
|
12753
13060
|
function mirrorEndpoints(file, host) {
|
|
12754
13061
|
const entry = file.mirrors?.[host];
|
|
12755
13062
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
@@ -12966,6 +13273,126 @@ var GRANTED_COMMAND_NAMES = LOCAL_BAKE_ROOT_COMMANDS.map((command) => command.na
|
|
|
12966
13273
|
// src/commands/uninstall.ts
|
|
12967
13274
|
init_dist();
|
|
12968
13275
|
|
|
13276
|
+
// src/daemon/pod-cpu-quota.ts
|
|
13277
|
+
init_safe_arg();
|
|
13278
|
+
var POD_SLICE = "kubepods.slice";
|
|
13279
|
+
function cpuMillicores(quantity) {
|
|
13280
|
+
const checked = assertQuantity(quantity, "reserveCpu");
|
|
13281
|
+
const match = /^(\d+(?:\.\d+)?)(m?)$/.exec(checked);
|
|
13282
|
+
if (!match?.[1])
|
|
13283
|
+
throw new Error(`Invalid reserveCpu: ${JSON.stringify(checked)} is not a CPU amount`);
|
|
13284
|
+
const amount = Number.parseFloat(match[1]);
|
|
13285
|
+
return Math.round(match[2] === "m" ? amount : amount * 1e3);
|
|
13286
|
+
}
|
|
13287
|
+
function podCpuQuotaPercent(cores, reservedMillicores) {
|
|
13288
|
+
const left = cores * 1e3 - reservedMillicores;
|
|
13289
|
+
if (left < 10) return null;
|
|
13290
|
+
return Math.floor(left / 10);
|
|
13291
|
+
}
|
|
13292
|
+
function setQuota(exec, value) {
|
|
13293
|
+
return exec("systemctl", ["set-property", POD_SLICE, `CPUQuota=${value}`]);
|
|
13294
|
+
}
|
|
13295
|
+
async function liftPodCpuQuota(exec) {
|
|
13296
|
+
const shown = await exec("systemctl", ["show", POD_SLICE, "-p", "CPUQuotaPerSecUSec", "--value"]);
|
|
13297
|
+
if (shown.exitCode !== 0 || ["", "infinity"].includes(shown.stdout.trim())) return null;
|
|
13298
|
+
const cleared = await setQuota(exec, "");
|
|
13299
|
+
return cleared.exitCode === 0 ? null : cleared.stderr.trim().slice(0, 200);
|
|
13300
|
+
}
|
|
13301
|
+
async function reconcilePodCpuQuota(exec, desired, warnings) {
|
|
13302
|
+
const reserve = desired.kubelet.systemReserved?.cpu;
|
|
13303
|
+
if (!reserve) {
|
|
13304
|
+
const failure = await liftPodCpuQuota(exec);
|
|
13305
|
+
if (failure) warnings.push(`Could not lift the CPU cap on ${POD_SLICE}: ${failure}`);
|
|
13306
|
+
return void 0;
|
|
13307
|
+
}
|
|
13308
|
+
const reserved = cpuMillicores(reserve);
|
|
13309
|
+
const state = await exec("systemctl", ["show", POD_SLICE, "-p", "ActiveState", "--value"]);
|
|
13310
|
+
if (state.exitCode !== 0 || state.stdout.trim() !== "active") {
|
|
13311
|
+
warnings.push(
|
|
13312
|
+
`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.`
|
|
13313
|
+
);
|
|
13314
|
+
return false;
|
|
13315
|
+
}
|
|
13316
|
+
const nproc = await exec("nproc", []);
|
|
13317
|
+
const cores = Number.parseInt(nproc.stdout.trim(), 10);
|
|
13318
|
+
if (nproc.exitCode !== 0 || !Number.isInteger(cores) || cores <= 0) {
|
|
13319
|
+
warnings.push(
|
|
13320
|
+
"Could not count this machine's CPUs, so the CPU you keep is weighted, not capped."
|
|
13321
|
+
);
|
|
13322
|
+
return false;
|
|
13323
|
+
}
|
|
13324
|
+
const percent = podCpuQuotaPercent(cores, reserved);
|
|
13325
|
+
if (percent === null) {
|
|
13326
|
+
warnings.push(
|
|
13327
|
+
`The CPU you keep (${reserve}) leaves no CPU for builds on this ${cores}-CPU machine. Lower it in your Conveyor profile.`
|
|
13328
|
+
);
|
|
13329
|
+
return false;
|
|
13330
|
+
}
|
|
13331
|
+
const set = await setQuota(exec, `${percent}%`);
|
|
13332
|
+
if (set.exitCode !== 0) {
|
|
13333
|
+
warnings.push(
|
|
13334
|
+
`Could not cap pod CPU at ${percent / 100} CPUs: ${set.stderr.trim().slice(0, 200)}`
|
|
13335
|
+
);
|
|
13336
|
+
return false;
|
|
13337
|
+
}
|
|
13338
|
+
return true;
|
|
13339
|
+
}
|
|
13340
|
+
|
|
13341
|
+
// src/daemon/registry-auth.ts
|
|
13342
|
+
init_dist();
|
|
13343
|
+
init_paths();
|
|
13344
|
+
var LOCAL_REGISTRY_AUTH_HOSTS = [
|
|
13345
|
+
PERSONAL_LOCAL_REGISTRY_HOST,
|
|
13346
|
+
new URL(PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT).host
|
|
13347
|
+
];
|
|
13348
|
+
function registryAuthConfigured(file, credentials) {
|
|
13349
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.every((host) => {
|
|
13350
|
+
const auth = file.configs?.[host]?.auth;
|
|
13351
|
+
return auth?.username === credentials.username && auth.password === credentials.password && auth.auth === void 0 && auth.identitytoken === void 0;
|
|
13352
|
+
});
|
|
13353
|
+
}
|
|
13354
|
+
function withLocalRegistryAuth(file, credentials) {
|
|
13355
|
+
return LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
13356
|
+
(next, host) => upsertRegistryAuth(next, host, {
|
|
13357
|
+
username: credentials.username,
|
|
13358
|
+
password: credentials.password
|
|
13359
|
+
}),
|
|
13360
|
+
file
|
|
13361
|
+
);
|
|
13362
|
+
}
|
|
13363
|
+
function monotonicNowUs() {
|
|
13364
|
+
return Number(process.hrtime.bigint() / 1000n);
|
|
13365
|
+
}
|
|
13366
|
+
async function k3sStartedAtMs(exec, nowMs, monotonicNow = monotonicNowUs) {
|
|
13367
|
+
const result = await exec(
|
|
13368
|
+
"systemctl",
|
|
13369
|
+
["show", "k3s", "--property=ActiveEnterTimestampMonotonic", "--value"],
|
|
13370
|
+
{ timeoutMs: 1e4 }
|
|
13371
|
+
).catch(() => null);
|
|
13372
|
+
if (!result || result.exitCode !== 0) return null;
|
|
13373
|
+
const startedUs = Number.parseInt(result.stdout.trim(), 10);
|
|
13374
|
+
if (!Number.isFinite(startedUs) || startedUs <= 0) return null;
|
|
13375
|
+
return nowMs - (monotonicNow() - startedUs) / 1e3;
|
|
13376
|
+
}
|
|
13377
|
+
async function settleRegistryAuth(ctx, credentials, written) {
|
|
13378
|
+
const now = (ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
13379
|
+
const raw = await ctx.fs.readFile(LOCAL_REGISTRY_AUTH_STATE_FILE);
|
|
13380
|
+
const credential = credentialFingerprint(credentials);
|
|
13381
|
+
const stored = parseRegistryAuthState(raw);
|
|
13382
|
+
const state = stored?.credential === credential ? stored : { credential, writtenAt: null, enforced: false };
|
|
13383
|
+
if (!state.enforced && (written || state.writtenAt === null)) {
|
|
13384
|
+
state.writtenAt = now.toISOString();
|
|
13385
|
+
}
|
|
13386
|
+
if (!state.enforced && state.writtenAt !== null) {
|
|
13387
|
+
const startedAt = await k3sStartedAtMs(ctx.exec, now.getTime(), ctx.monotonicNowUs);
|
|
13388
|
+
if (startedAt !== null && startedAt > Date.parse(state.writtenAt)) state.enforced = true;
|
|
13389
|
+
}
|
|
13390
|
+
const next = `${JSON.stringify(state)}
|
|
13391
|
+
`;
|
|
13392
|
+
if (next !== raw) await ctx.fs.writeFile(LOCAL_REGISTRY_AUTH_STATE_FILE, next, 384);
|
|
13393
|
+
return state.enforced;
|
|
13394
|
+
}
|
|
13395
|
+
|
|
12969
13396
|
// src/lib/isolation.ts
|
|
12970
13397
|
init_dist();
|
|
12971
13398
|
|
|
@@ -13234,17 +13661,36 @@ async function removeLocalRegistry(ports) {
|
|
|
13234
13661
|
`Could not remove the local image registry from the cluster: ${removed.stderr.trim().slice(0, 200)}`
|
|
13235
13662
|
);
|
|
13236
13663
|
}
|
|
13664
|
+
for (const [namespace, name2] of [
|
|
13665
|
+
[PERSONAL_LOCAL_REGISTRY_NAMESPACE, LOCAL_REGISTRY_HTPASSWD_SECRET],
|
|
13666
|
+
[PERSONAL_BAKE_NAMESPACE, PERSONAL_LOCAL_REGISTRY_PUSH_SECRET]
|
|
13667
|
+
]) {
|
|
13668
|
+
const secret = await ports.exec(
|
|
13669
|
+
"k3s",
|
|
13670
|
+
["kubectl", "-n", namespace, "delete", "secret", name2, "--ignore-not-found"],
|
|
13671
|
+
{ timeoutMs: 6e4 }
|
|
13672
|
+
);
|
|
13673
|
+
if (secret.exitCode !== 0) {
|
|
13674
|
+
ports.log(
|
|
13675
|
+
`Could not remove the local image registry's Secret ${namespace}/${name2}: ${secret.stderr.trim().slice(0, 200)}`
|
|
13676
|
+
);
|
|
13677
|
+
}
|
|
13678
|
+
}
|
|
13237
13679
|
const raw = await ports.fs.readFile(REGISTRIES_FILE);
|
|
13238
13680
|
const parsed = parseRegistries(raw);
|
|
13239
|
-
|
|
13681
|
+
const next = LOCAL_REGISTRY_AUTH_HOSTS.reduce(
|
|
13682
|
+
removeRegistryAuth,
|
|
13683
|
+
removeRegistryMirror(parsed, PERSONAL_LOCAL_REGISTRY_HOST)
|
|
13684
|
+
);
|
|
13685
|
+
if (serializeRegistries(next) === serializeRegistries(parsed)) return;
|
|
13240
13686
|
const backup = await writeRegistriesWithBackup(
|
|
13241
13687
|
ports.fs,
|
|
13242
13688
|
raw,
|
|
13243
|
-
|
|
13689
|
+
next,
|
|
13244
13690
|
(ports.now ?? (() => /* @__PURE__ */ new Date()))()
|
|
13245
13691
|
);
|
|
13246
13692
|
ports.log(
|
|
13247
|
-
`Removed the local image registry's mirror from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
13693
|
+
`Removed the local image registry's mirror and credential from ${REGISTRIES_FILE} (backup: ${backup}).`
|
|
13248
13694
|
);
|
|
13249
13695
|
}
|
|
13250
13696
|
async function removeIsolation(ports) {
|
|
@@ -13301,6 +13747,10 @@ async function uninstall(ports, options = {}) {
|
|
|
13301
13747
|
}
|
|
13302
13748
|
await removeLocalRegistry(ports);
|
|
13303
13749
|
await removeIsolation(ports);
|
|
13750
|
+
const lifted = await liftPodCpuQuota(ports.exec).catch(
|
|
13751
|
+
(error) => error instanceof Error ? error.message : String(error)
|
|
13752
|
+
);
|
|
13753
|
+
if (lifted) ports.log(`Could not lift Conveyor's CPU cap on kubepods.slice: ${lifted}`);
|
|
13304
13754
|
for (const path of OWNED_FILES) {
|
|
13305
13755
|
await ports.fs.removeFile(path);
|
|
13306
13756
|
result.removedFiles.push(path);
|
|
@@ -13353,6 +13803,74 @@ async function uninstall(ports, options = {}) {
|
|
|
13353
13803
|
return result;
|
|
13354
13804
|
}
|
|
13355
13805
|
|
|
13806
|
+
// src/lib/bins.ts
|
|
13807
|
+
var INSTALLER_PATHS = {
|
|
13808
|
+
bun: "/root/.bun/bin/bun",
|
|
13809
|
+
cli: "/root/.bun/bin/conveyor-k3",
|
|
13810
|
+
cloudflared: "/usr/local/bin/cloudflared"
|
|
13811
|
+
};
|
|
13812
|
+
async function whichOrNull(exec, bin) {
|
|
13813
|
+
const result = await exec("which", [bin]).catch(() => null);
|
|
13814
|
+
if (!result || result.exitCode !== 0) return null;
|
|
13815
|
+
const path = result.stdout.trim().split("\n")[0]?.trim();
|
|
13816
|
+
return path && path.startsWith("/") ? path : null;
|
|
13817
|
+
}
|
|
13818
|
+
async function resolveBinaries(exec, state, selfPath) {
|
|
13819
|
+
const recorded = state.bins;
|
|
13820
|
+
const cliSelf = selfPath?.startsWith("/") ? selfPath : null;
|
|
13821
|
+
return {
|
|
13822
|
+
bun: recorded?.bun ?? await whichOrNull(exec, "bun") ?? INSTALLER_PATHS.bun,
|
|
13823
|
+
// `cli` prefers the running process's own path: if this code is executing,
|
|
13824
|
+
// that path is by definition correct.
|
|
13825
|
+
cli: cliSelf ?? recorded?.cli ?? await whichOrNull(exec, "conveyor-k3") ?? INSTALLER_PATHS.cli,
|
|
13826
|
+
cloudflared: recorded?.cloudflared ?? await whichOrNull(exec, "cloudflared") ?? INSTALLER_PATHS.cloudflared
|
|
13827
|
+
};
|
|
13828
|
+
}
|
|
13829
|
+
var CLI_PATH_LINK = "/usr/local/bin/conveyor-k3";
|
|
13830
|
+
async function linkCliOnPath(exec, target2 = INSTALLER_PATHS.cli) {
|
|
13831
|
+
if (target2 === CLI_PATH_LINK) return { linked: false };
|
|
13832
|
+
const present = await exec("test", ["-x", target2]).catch(() => null);
|
|
13833
|
+
if (present?.exitCode !== 0) return { linked: false };
|
|
13834
|
+
const link = await exec("ln", ["-sfn", target2, CLI_PATH_LINK]).catch(() => null);
|
|
13835
|
+
if (link?.exitCode === 0) return { linked: true };
|
|
13836
|
+
return {
|
|
13837
|
+
linked: false,
|
|
13838
|
+
problem: `Could not link ${CLI_PATH_LINK} to ${target2}: ${link?.stderr.trim().slice(0, 200) || "ln failed"}`
|
|
13839
|
+
};
|
|
13840
|
+
}
|
|
13841
|
+
var NPM_GLOBAL_PACKAGE_DIRS = [
|
|
13842
|
+
"/usr/local/lib/node_modules/@rallycry/conveyor-k3",
|
|
13843
|
+
"/usr/lib/node_modules/@rallycry/conveyor-k3"
|
|
13844
|
+
];
|
|
13845
|
+
async function npmCopyHint(readFile9) {
|
|
13846
|
+
for (const dir of NPM_GLOBAL_PACKAGE_DIRS) {
|
|
13847
|
+
if (await readFile9(`${dir}/package.json`) !== null) {
|
|
13848
|
+
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`;
|
|
13849
|
+
}
|
|
13850
|
+
}
|
|
13851
|
+
return null;
|
|
13852
|
+
}
|
|
13853
|
+
|
|
13854
|
+
// src/lib/cloudflared.ts
|
|
13855
|
+
var PACKAGE_MANAGER_REFUSAL = "installed by a package manager";
|
|
13856
|
+
async function resolveRealPath(exec, bin) {
|
|
13857
|
+
const result = await exec("readlink", ["-f", bin]).catch(() => null);
|
|
13858
|
+
const resolved = result?.exitCode === 0 ? result.stdout.trim() : "";
|
|
13859
|
+
return resolved.startsWith("/") ? resolved : bin;
|
|
13860
|
+
}
|
|
13861
|
+
async function cloudflaredUpdater(exec, bin) {
|
|
13862
|
+
const real = await resolveRealPath(exec, bin);
|
|
13863
|
+
const owner = await exec("dpkg", ["-S", real]).catch(() => null);
|
|
13864
|
+
return owner?.exitCode === 0 ? "package-manager" : "conveyor";
|
|
13865
|
+
}
|
|
13866
|
+
function isPackageManagerRefusal(output) {
|
|
13867
|
+
return `${output.stdout}
|
|
13868
|
+
${output.stderr}`.toLowerCase().includes(PACKAGE_MANAGER_REFUSAL);
|
|
13869
|
+
}
|
|
13870
|
+
function describeCloudflaredUpdater(updater) {
|
|
13871
|
+
return updater === "package-manager" ? "cloudflared is updated by your package manager, not by Conveyor" : "cloudflared is updated by Conveyor's daily update";
|
|
13872
|
+
}
|
|
13873
|
+
|
|
13356
13874
|
// src/commands/update.ts
|
|
13357
13875
|
init_paths();
|
|
13358
13876
|
var CLOUDFLARED_UPDATED_EXIT = 11;
|
|
@@ -13441,7 +13959,22 @@ async function updatePackage(ports, result, targetVersion) {
|
|
|
13441
13959
|
await ports.exec("systemctl", ["restart", DAEMON_UNIT]);
|
|
13442
13960
|
}
|
|
13443
13961
|
async function updateCloudflared(ports, result) {
|
|
13962
|
+
const packaged = () => {
|
|
13963
|
+
result.cloudflaredUpdater = "package-manager";
|
|
13964
|
+
result.notes.push(
|
|
13965
|
+
`cloudflared update skipped: ${describeCloudflaredUpdater("package-manager")}`
|
|
13966
|
+
);
|
|
13967
|
+
};
|
|
13968
|
+
if (await cloudflaredUpdater(ports.exec, ports.bins.cloudflared) === "package-manager") {
|
|
13969
|
+
packaged();
|
|
13970
|
+
return;
|
|
13971
|
+
}
|
|
13444
13972
|
const update = await ports.exec(ports.bins.cloudflared, ["update"], { timeoutMs: 5 * 6e4 });
|
|
13973
|
+
if (isPackageManagerRefusal(update)) {
|
|
13974
|
+
packaged();
|
|
13975
|
+
return;
|
|
13976
|
+
}
|
|
13977
|
+
result.cloudflaredUpdater = "conveyor";
|
|
13445
13978
|
if (update.exitCode !== CLOUDFLARED_UPDATED_EXIT) return;
|
|
13446
13979
|
result.cloudflaredUpdated = true;
|
|
13447
13980
|
ports.log("cloudflared was updated; restarting the tunnel.");
|
|
@@ -13451,10 +13984,13 @@ async function runUpdate(ports, decision, channel, targetVersion) {
|
|
|
13451
13984
|
const result = {
|
|
13452
13985
|
packageUpdated: false,
|
|
13453
13986
|
cloudflaredUpdated: false,
|
|
13987
|
+
cloudflaredUpdater: null,
|
|
13454
13988
|
k3sUpgraded: false,
|
|
13455
13989
|
notes: []
|
|
13456
13990
|
};
|
|
13457
13991
|
await updatePackage(ports, result, targetVersion);
|
|
13992
|
+
const linked = await linkCliOnPath(ports.exec);
|
|
13993
|
+
if (linked.problem) result.notes.push(linked.problem);
|
|
13458
13994
|
await updateCloudflared(ports, result);
|
|
13459
13995
|
if (!decision.upgrade) {
|
|
13460
13996
|
result.notes.push(`k3s upgrade skipped: ${decision.reason}`);
|
|
@@ -13656,7 +14192,7 @@ async function ensureCaPool(ports) {
|
|
|
13656
14192
|
const existing = await ports.fs.readFile(CA_POOL_PATH);
|
|
13657
14193
|
if (existing !== ca) await ports.fs.writeFile(CA_POOL_PATH, ca, 420);
|
|
13658
14194
|
}
|
|
13659
|
-
async function
|
|
14195
|
+
async function ensureServiceAccount(ports) {
|
|
13660
14196
|
await runOrThrow(ports.exec, "k3s", [
|
|
13661
14197
|
"kubectl",
|
|
13662
14198
|
"create",
|
|
@@ -13685,6 +14221,9 @@ async function mintServiceAccountToken(ports) {
|
|
|
13685
14221
|
stdin: result.stdout
|
|
13686
14222
|
});
|
|
13687
14223
|
});
|
|
14224
|
+
}
|
|
14225
|
+
async function mintServiceAccountToken(ports) {
|
|
14226
|
+
await ensureServiceAccount(ports);
|
|
13688
14227
|
const token = await runOrThrow(ports.exec, "k3s", [
|
|
13689
14228
|
"kubectl",
|
|
13690
14229
|
"-n",
|
|
@@ -13752,13 +14291,13 @@ function buildConveyorSocketOptions(auth) {
|
|
|
13752
14291
|
|
|
13753
14292
|
// src/daemon/control-endpoint.ts
|
|
13754
14293
|
import { lookup as dnsLookup } from "dns";
|
|
13755
|
-
import { existsSync, readFileSync } from "fs";
|
|
14294
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
13756
14295
|
import { Agent } from "https";
|
|
13757
14296
|
var CONTROL_FILE = "/etc/conveyor-vm/control.json";
|
|
13758
14297
|
var CONTROL_ALIAS_IP = /^192\.168\.5\.24[0-7]$/;
|
|
13759
14298
|
function readControlFile(path) {
|
|
13760
14299
|
try {
|
|
13761
|
-
return
|
|
14300
|
+
return readFileSync2(path, "utf8");
|
|
13762
14301
|
} catch {
|
|
13763
14302
|
return null;
|
|
13764
14303
|
}
|
|
@@ -13804,8 +14343,8 @@ function controlPlaneRoute(apiUrl, readFile9 = readControlFile, fileExists = exi
|
|
|
13804
14343
|
}
|
|
13805
14344
|
|
|
13806
14345
|
// src/daemon/device-proof.ts
|
|
13807
|
-
import { lstatSync, readFileSync as
|
|
13808
|
-
import { createPrivateKey, randomBytes, sign } from "crypto";
|
|
14346
|
+
import { lstatSync, readFileSync as readFileSync3 } from "fs";
|
|
14347
|
+
import { createPrivateKey, randomBytes as randomBytes2, sign } from "crypto";
|
|
13809
14348
|
var DEVICE_KEY_PATH = "/etc/conveyor/device-key.pem";
|
|
13810
14349
|
function privateDeviceKey(path, ownerUid) {
|
|
13811
14350
|
let stat2;
|
|
@@ -13819,7 +14358,7 @@ function privateDeviceKey(path, ownerUid) {
|
|
|
13819
14358
|
throw new Error("Conveyor device key must be a regular file.");
|
|
13820
14359
|
if ((stat2.mode & 511) !== 384 || stat2.uid !== ownerUid)
|
|
13821
14360
|
throw new Error("Conveyor device key must be owned by root with mode 0600.");
|
|
13822
|
-
const pem =
|
|
14361
|
+
const pem = readFileSync3(path, "utf8");
|
|
13823
14362
|
if (Buffer.byteLength(pem) > 16 * 1024) throw new Error("Conveyor device key is too large.");
|
|
13824
14363
|
const key = createPrivateKey(pem);
|
|
13825
14364
|
if (key.asymmetricKeyType !== "ed25519")
|
|
@@ -13829,7 +14368,7 @@ function privateDeviceKey(path, ownerUid) {
|
|
|
13829
14368
|
function createClusterProof(apiUrl, clusterToken, key) {
|
|
13830
14369
|
const origin = new URL(apiUrl).origin;
|
|
13831
14370
|
const timestamp = Date.now();
|
|
13832
|
-
const nonce =
|
|
14371
|
+
const nonce = randomBytes2(32).toString("base64url");
|
|
13833
14372
|
const message3 = `conveyor-device-v1
|
|
13834
14373
|
${origin}
|
|
13835
14374
|
${clusterToken}
|
|
@@ -14003,7 +14542,7 @@ var ClusterConnection = class {
|
|
|
14003
14542
|
};
|
|
14004
14543
|
|
|
14005
14544
|
// src/lib/manifest-guard.ts
|
|
14006
|
-
import { isMap, isScalar, parseAllDocuments } from "yaml";
|
|
14545
|
+
import { isAlias, isMap, isScalar, parseAllDocuments } from "yaml";
|
|
14007
14546
|
var ALLOWED_MANIFEST_KINDS = [
|
|
14008
14547
|
"Namespace",
|
|
14009
14548
|
"Role",
|
|
@@ -14015,6 +14554,14 @@ var ALLOWED_MANIFEST_KINDS = [
|
|
|
14015
14554
|
var ALLOWED = new Set(ALLOWED_MANIFEST_KINDS);
|
|
14016
14555
|
var MISSING_KIND = "(missing kind)";
|
|
14017
14556
|
var UNPARSEABLE = "(unparseable)";
|
|
14557
|
+
var LIST_SHAPED = "(list-shaped document)";
|
|
14558
|
+
function isListShaped(map) {
|
|
14559
|
+
return map.items.some(({ key }) => {
|
|
14560
|
+
if (isAlias(key)) return true;
|
|
14561
|
+
const name2 = isScalar(key) ? key.value : key;
|
|
14562
|
+
return typeof name2 === "string" && (name2 === "<<" || name2.toLowerCase() === "items");
|
|
14563
|
+
});
|
|
14564
|
+
}
|
|
14018
14565
|
function findDisallowedKinds(manifest) {
|
|
14019
14566
|
let documents;
|
|
14020
14567
|
try {
|
|
@@ -14037,6 +14584,10 @@ function findDisallowedKinds(manifest) {
|
|
|
14037
14584
|
record2(MISSING_KIND);
|
|
14038
14585
|
continue;
|
|
14039
14586
|
}
|
|
14587
|
+
if (isListShaped(doc.contents)) {
|
|
14588
|
+
record2(LIST_SHAPED);
|
|
14589
|
+
continue;
|
|
14590
|
+
}
|
|
14040
14591
|
const kind = doc.get("kind");
|
|
14041
14592
|
if (typeof kind !== "string" || kind.length === 0) {
|
|
14042
14593
|
record2(MISSING_KIND);
|
|
@@ -14046,14 +14597,64 @@ function findDisallowedKinds(manifest) {
|
|
|
14046
14597
|
}
|
|
14047
14598
|
return disallowed;
|
|
14048
14599
|
}
|
|
14600
|
+
function withoutKind(manifest, kind) {
|
|
14601
|
+
return parseAllDocuments(manifest).filter((doc) => !(isMap(doc.contents) && doc.get("kind") === kind)).map((doc) => doc.toString()).join("---\n");
|
|
14602
|
+
}
|
|
14603
|
+
function declaresPriorityClass(manifest, name2, value) {
|
|
14604
|
+
return parseAllDocuments(manifest).some(
|
|
14605
|
+
(doc) => isMap(doc.contents) && doc.get("kind") === "PriorityClass" && doc.getIn(["metadata", "name"]) === name2 && doc.get("value") === value
|
|
14606
|
+
);
|
|
14607
|
+
}
|
|
14049
14608
|
|
|
14050
14609
|
// src/daemon/reconcile.ts
|
|
14051
14610
|
init_safe_arg();
|
|
14611
|
+
|
|
14612
|
+
// src/daemon/kubelet-upkeep.ts
|
|
14052
14613
|
init_paths();
|
|
14614
|
+
function ownerRestartWarning(change) {
|
|
14615
|
+
return `${change} Conveyor did not install this server's k3s, so it never restarts it: run \`sudo systemctl restart k3s\` when no build runs.`;
|
|
14616
|
+
}
|
|
14617
|
+
async function reconcileKubelet(ctx, desired, result, managed) {
|
|
14618
|
+
const ownerConfig = await ctx.fs.readFile(K3S_OWNER_CONFIG);
|
|
14619
|
+
const rendered = renderK3sDropIn(desired.kubelet, ownerConfig, { disableComponents: managed });
|
|
14620
|
+
result.skippedKubeletFlags = rendered.skipped;
|
|
14621
|
+
result.effectiveImageGcHighThreshold = rendered.skipped.includes("image-gc-high-threshold") ? ownerImageGcHighThreshold(ownerConfig) : desired.kubelet.imageGcHighThreshold;
|
|
14622
|
+
for (const flag of rendered.skipped) {
|
|
14623
|
+
result.warnings.push(
|
|
14624
|
+
`Left \`${flag}\` to your own /etc/rancher/k3s/config.yaml \u2014 Conveyor does not override a setting you chose.`
|
|
14625
|
+
);
|
|
14626
|
+
}
|
|
14627
|
+
const current = await ctx.fs.readFile(K3S_CONFIG_DROPIN);
|
|
14628
|
+
if (rendered.content === null) {
|
|
14629
|
+
if (current !== null) await ctx.fs.removeFile(K3S_CONFIG_DROPIN);
|
|
14630
|
+
return;
|
|
14631
|
+
}
|
|
14632
|
+
if (current === rendered.content) return;
|
|
14633
|
+
await ctx.fs.ensureDir(K3S_CONFIG_DROPIN.slice(0, K3S_CONFIG_DROPIN.lastIndexOf("/")));
|
|
14634
|
+
await ctx.fs.writeFile(K3S_CONFIG_DROPIN, rendered.content, 420);
|
|
14635
|
+
if (!managed) {
|
|
14636
|
+
result.warnings.push(ownerRestartWarning("Kubelet settings changed."));
|
|
14637
|
+
return;
|
|
14638
|
+
}
|
|
14639
|
+
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
14640
|
+
result.warnings.push(
|
|
14641
|
+
`Kubelet settings changed but ${busyReason(ctx.runningConveyorPods)} \u2014 the restart is deferred until the machine is idle.`
|
|
14642
|
+
);
|
|
14643
|
+
return;
|
|
14644
|
+
}
|
|
14645
|
+
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
14646
|
+
if (restart.exitCode === 0) {
|
|
14647
|
+
result.restartedK3s = true;
|
|
14648
|
+
} else {
|
|
14649
|
+
result.warnings.push(
|
|
14650
|
+
`Could not restart k3s to apply new kubelet settings: ${restart.stderr.trim().slice(0, 200)}`
|
|
14651
|
+
);
|
|
14652
|
+
}
|
|
14653
|
+
}
|
|
14053
14654
|
|
|
14054
14655
|
// src/daemon/local-registry-upkeep.ts
|
|
14055
14656
|
init_dist();
|
|
14056
|
-
import { createHash } from "crypto";
|
|
14657
|
+
import { createHash as createHash3 } from "crypto";
|
|
14057
14658
|
init_paths();
|
|
14058
14659
|
function nowDate() {
|
|
14059
14660
|
return /* @__PURE__ */ new Date();
|
|
@@ -14062,33 +14663,47 @@ function mirrorConfigured(raw) {
|
|
|
14062
14663
|
const endpoints = mirrorEndpoints(parseRegistries(raw), PERSONAL_LOCAL_REGISTRY_HOST);
|
|
14063
14664
|
return endpoints.length === 1 && endpoints[0] === PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT;
|
|
14064
14665
|
}
|
|
14065
|
-
async function applyUnlessCollecting(ctx) {
|
|
14666
|
+
async function applyUnlessCollecting(ctx, auth) {
|
|
14066
14667
|
const deployment = await readRegistryDeployment(ctx.exec);
|
|
14067
14668
|
if (!deployment) throw new Error("the registry's Deployment could not be read");
|
|
14068
14669
|
if (gcMarkIsLive(deployment.mark, (ctx.now ?? nowDate)().getTime(), ctx.isAlive)) return false;
|
|
14069
14670
|
if (deployment.mark !== null && !await deleteSweepJob(ctx.exec)) {
|
|
14070
14671
|
throw new Error("a stopped cleanup's Job could not be deleted, so the registry stays stopped");
|
|
14071
14672
|
}
|
|
14072
|
-
await kubectlApply(ctx.exec, renderLocalRegistryManifests());
|
|
14673
|
+
await kubectlApply(ctx.exec, renderLocalRegistryManifests(auth));
|
|
14073
14674
|
if (deployment.mark !== null) await clearGcMark(ctx.exec);
|
|
14074
14675
|
return true;
|
|
14075
14676
|
}
|
|
14076
14677
|
async function deployLocalRegistry(ctx) {
|
|
14077
14678
|
await ctx.fs.ensureDir(LOCAL_REGISTRY_DATA_DIR, 448);
|
|
14078
|
-
const
|
|
14679
|
+
const credentials = await loadOrCreateRegistryCredentials(ctx.fs, ctx.random);
|
|
14079
14680
|
const raw = await ctx.fs.readFile(REGISTRIES_FILE);
|
|
14080
|
-
|
|
14081
|
-
const
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14681
|
+
let file = parseRegistries(raw);
|
|
14682
|
+
const mirrorWritten = !mirrorConfigured(raw);
|
|
14683
|
+
const authWritten = !registryAuthConfigured(file, credentials);
|
|
14684
|
+
let backupPath = null;
|
|
14685
|
+
if (mirrorWritten) {
|
|
14686
|
+
file = upsertRegistryMirror(file, PERSONAL_LOCAL_REGISTRY_HOST, [
|
|
14687
|
+
PERSONAL_LOCAL_REGISTRY_NODE_ENDPOINT
|
|
14688
|
+
]);
|
|
14689
|
+
}
|
|
14690
|
+
if (authWritten) file = withLocalRegistryAuth(file, credentials);
|
|
14691
|
+
if (mirrorWritten || authWritten) {
|
|
14692
|
+
backupPath = await writeRegistriesWithBackup(ctx.fs, raw, file, (ctx.now ?? nowDate)());
|
|
14693
|
+
}
|
|
14694
|
+
const authEnforced = await settleRegistryAuth(ctx, credentials, authWritten);
|
|
14695
|
+
await kubectlApply(ctx.exec, renderLocalRegistrySecrets(credentials));
|
|
14696
|
+
const applied = await applyUnlessCollecting(ctx, authEnforced ? credentials : null);
|
|
14697
|
+
return { applied, mirrorWritten, authWritten, backupPath, authEnforced };
|
|
14086
14698
|
}
|
|
14087
14699
|
async function readMirrorState(fs2) {
|
|
14088
14700
|
const raw = await fs2.readFile(REGISTRIES_FILE);
|
|
14089
|
-
const
|
|
14701
|
+
const authPending = !await registryAuthEnforced((path) => fs2.readFile(path));
|
|
14702
|
+
const fingerprint = createHash3("sha256").update(raw ?? "").digest("hex");
|
|
14090
14703
|
if (!mirrorConfigured(raw)) return { state: "absent", fingerprint };
|
|
14091
|
-
if (await mirrorLoaded((path) => fs2.readFile(path)))
|
|
14704
|
+
if (!authPending && await mirrorLoaded((path) => fs2.readFile(path))) {
|
|
14705
|
+
return { state: "loaded", fingerprint };
|
|
14706
|
+
}
|
|
14092
14707
|
const tried = (await fs2.readFile(LOCAL_REGISTRY_MIRROR_RESTART_FILE))?.trim() === fingerprint;
|
|
14093
14708
|
return { state: tried ? "tried" : "owed", fingerprint };
|
|
14094
14709
|
}
|
|
@@ -14100,14 +14715,14 @@ async function loadRegistryMirror(ctx) {
|
|
|
14100
14715
|
if (state === "tried") {
|
|
14101
14716
|
return {
|
|
14102
14717
|
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}.`
|
|
14718
|
+
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
14719
|
};
|
|
14105
14720
|
}
|
|
14106
14721
|
if (state !== "owed") return { restarted: false, warning: null };
|
|
14107
14722
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
14108
14723
|
return {
|
|
14109
14724
|
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.`
|
|
14725
|
+
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
14726
|
};
|
|
14112
14727
|
}
|
|
14113
14728
|
const restart = await ctx.exec("systemctl", ["restart", "k3s"], { timeoutMs: 12e4 });
|
|
@@ -14138,7 +14753,18 @@ async function stdoutOf(exec, bin, args) {
|
|
|
14138
14753
|
const run = await exec(bin, args, { timeoutMs: 1e4 }).catch(() => null);
|
|
14139
14754
|
return run && run.exitCode === 0 ? run.stdout : null;
|
|
14140
14755
|
}
|
|
14756
|
+
async function dropInDirExists(exec) {
|
|
14757
|
+
const run = await exec("test", ["-d", K3S_CONFIG_DROPIN_DIR], { timeoutMs: 1e4 }).catch(
|
|
14758
|
+
() => null
|
|
14759
|
+
);
|
|
14760
|
+
if (run === null) return null;
|
|
14761
|
+
if (run.exitCode === 0) return true;
|
|
14762
|
+
return run.exitCode === 1 && run.stderr.trim() === "" ? false : null;
|
|
14763
|
+
}
|
|
14141
14764
|
async function readK3sConfigs(ctx) {
|
|
14765
|
+
const dirExists = await dropInDirExists(ctx.exec);
|
|
14766
|
+
if (dirExists === null) return null;
|
|
14767
|
+
if (!dirExists) return [await ctx.fs.readFile(K3S_OWNER_CONFIG)];
|
|
14142
14768
|
const listed = await stdoutOf(ctx.exec, "find", [
|
|
14143
14769
|
"-L",
|
|
14144
14770
|
K3S_CONFIG_DROPIN_DIR,
|
|
@@ -14504,14 +15130,22 @@ async function readImageInventory(exec) {
|
|
|
14504
15130
|
}
|
|
14505
15131
|
return { images, referencedIds };
|
|
14506
15132
|
}
|
|
15133
|
+
var CRICTL_RMI_TIMEOUT = "120s";
|
|
15134
|
+
var RMI_EXEC_TIMEOUT_MS = 15e4;
|
|
15135
|
+
var DEADLINE_EXCEEDED = "context deadline exceeded";
|
|
14507
15136
|
async function pruneStrandedImages(exec, inventory, wantedImages) {
|
|
14508
15137
|
const candidates = selectStrandedImages(inventory, wantedImages);
|
|
14509
|
-
const result = { removed: 0, bytesReclaimed: 0, failures: [] };
|
|
15138
|
+
const result = { removed: 0, bytesReclaimed: 0, failures: [], pending: [] };
|
|
14510
15139
|
for (const candidate of candidates) {
|
|
14511
|
-
const removal = await exec("crictl", ["rmi", candidate.id]
|
|
15140
|
+
const removal = await exec("crictl", ["--timeout", CRICTL_RMI_TIMEOUT, "rmi", candidate.id], {
|
|
15141
|
+
timeoutMs: RMI_EXEC_TIMEOUT_MS
|
|
15142
|
+
});
|
|
14512
15143
|
if (removal.exitCode === 0) {
|
|
14513
15144
|
result.removed += 1;
|
|
14514
15145
|
result.bytesReclaimed += candidate.sizeBytes;
|
|
15146
|
+
} else if (`${removal.stderr}
|
|
15147
|
+
${removal.stdout}`.includes(DEADLINE_EXCEEDED)) {
|
|
15148
|
+
result.pending.push(candidate.id);
|
|
14515
15149
|
} else {
|
|
14516
15150
|
result.failures.push(`${candidate.id}: ${removal.stderr.trim().slice(0, 120)}`);
|
|
14517
15151
|
}
|
|
@@ -14613,17 +15247,26 @@ function countPinnedWanted(inventory, wantedImages) {
|
|
|
14613
15247
|
// src/daemon/telemetry.ts
|
|
14614
15248
|
init_paths();
|
|
14615
15249
|
var LOCAL_REGISTRY_WARN_AFTER_MS = 10 * 6e4;
|
|
15250
|
+
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
15251
|
async function reportLocalRegistry(ports, watch2, now = Date.now()) {
|
|
14617
15252
|
if (!watch2) return { fields: {}, warnings: [] };
|
|
14618
|
-
const ready = await
|
|
15253
|
+
const { ready, authEnforced } = await localRegistryStatus({
|
|
15254
|
+
readFile: ports.readFile,
|
|
15255
|
+
fetch: ports.fetch
|
|
15256
|
+
});
|
|
14619
15257
|
const paused = (watch2.pausedUntil ?? 0) > now;
|
|
14620
15258
|
watch2.notReadySince = ready || paused ? null : watch2.notReadySince ?? now;
|
|
14621
15259
|
const stuckMs = watch2.notReadySince === null ? 0 : now - watch2.notReadySince;
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
warnings
|
|
15260
|
+
const warnings = [];
|
|
15261
|
+
if (stuckMs >= LOCAL_REGISTRY_WARN_AFTER_MS) {
|
|
15262
|
+
warnings.push(
|
|
14625
15263
|
`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
|
-
|
|
15264
|
+
);
|
|
15265
|
+
}
|
|
15266
|
+
if (!authEnforced) warnings.push(LOCAL_REGISTRY_OPEN_WARNING);
|
|
15267
|
+
return {
|
|
15268
|
+
fields: { localRegistry: { host: PERSONAL_LOCAL_REGISTRY_HOST, ready, authEnforced } },
|
|
15269
|
+
warnings
|
|
14627
15270
|
};
|
|
14628
15271
|
}
|
|
14629
15272
|
var VM_CAPACITY_FILE = "/etc/conveyor-vm/capacity.json";
|
|
@@ -14746,6 +15389,7 @@ async function collectHeartbeat(ports, state, inventory) {
|
|
|
14746
15389
|
...localImages ? { localImages } : {},
|
|
14747
15390
|
...registry.fields,
|
|
14748
15391
|
...state.isolation ? { isolation: state.isolation } : {},
|
|
15392
|
+
...state.cpuQuotaApplied === void 0 ? {} : { cpuQuotaApplied: state.cpuQuotaApplied },
|
|
14749
15393
|
localWarnings: withinHeartbeatBounds([
|
|
14750
15394
|
...state.localWarnings,
|
|
14751
15395
|
...gcWarning ? [gcWarning] : [],
|
|
@@ -14796,7 +15440,15 @@ async function collect(ports, now) {
|
|
|
14796
15440
|
if (watch2) watch2.pausedUntil = now.getTime() + REGISTRY_GC_MARK_TTL_MS;
|
|
14797
15441
|
try {
|
|
14798
15442
|
const outcome = await collectRegistryGarbage(
|
|
14799
|
-
{
|
|
15443
|
+
{
|
|
15444
|
+
exec,
|
|
15445
|
+
log: log2,
|
|
15446
|
+
requestDesiredState,
|
|
15447
|
+
fetch: fetch2,
|
|
15448
|
+
readFile: (path) => ports.fs.readFile(path),
|
|
15449
|
+
sleep: sleep4,
|
|
15450
|
+
isAlive
|
|
15451
|
+
},
|
|
14800
15452
|
{ dryRun: false }
|
|
14801
15453
|
);
|
|
14802
15454
|
if (outcome.skipped) return outcome.skipped;
|
|
@@ -14842,48 +15494,7 @@ async function runRegistryUpkeep(ports, lastGcAt, maintenanceHour) {
|
|
|
14842
15494
|
}
|
|
14843
15495
|
|
|
14844
15496
|
// 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");
|
|
15497
|
+
async function livePriorityClassValue(ctx, name2) {
|
|
14887
15498
|
const read = await ctx.exec("k3s", [
|
|
14888
15499
|
"kubectl",
|
|
14889
15500
|
"get",
|
|
@@ -14892,30 +15503,59 @@ async function reconcilePriorityClass(ctx, desired, result) {
|
|
|
14892
15503
|
"-o",
|
|
14893
15504
|
"jsonpath={.value}"
|
|
14894
15505
|
]);
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
15506
|
+
const value = Number.parseInt(read.stdout.trim(), 10);
|
|
15507
|
+
return read.exitCode === 0 && Number.isFinite(value) ? value : null;
|
|
15508
|
+
}
|
|
15509
|
+
async function reconcilePriorityClass(ctx, desired, result) {
|
|
15510
|
+
const name2 = assertK8sName(desired.priorityClass.name, "priorityClass.name");
|
|
15511
|
+
const wanted = desired.priorityClass.value;
|
|
15512
|
+
const existingValue = await livePriorityClassValue(ctx, name2);
|
|
15513
|
+
if (existingValue === null || existingValue === wanted) return "ready";
|
|
15514
|
+
if (!declaresPriorityClass(desired.rbacManifest, name2, wanted)) {
|
|
15515
|
+
result.warnings.push(
|
|
15516
|
+
`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.`
|
|
15517
|
+
);
|
|
15518
|
+
return "kept";
|
|
15519
|
+
}
|
|
14898
15520
|
if (!mayDisrupt(ctx.runningConveyorPods)) {
|
|
14899
15521
|
result.warnings.push(
|
|
14900
|
-
`The \`${name2}\` PriorityClass has value ${existingValue}, but Conveyor needs ${
|
|
15522
|
+
`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
15523
|
);
|
|
14902
|
-
return;
|
|
15524
|
+
return "kept";
|
|
14903
15525
|
}
|
|
14904
|
-
const removed = await ctx.exec("k3s", [
|
|
15526
|
+
const removed = await ctx.exec("k3s", [
|
|
15527
|
+
"kubectl",
|
|
15528
|
+
"delete",
|
|
15529
|
+
"priorityclass",
|
|
15530
|
+
name2,
|
|
15531
|
+
"--ignore-not-found"
|
|
15532
|
+
]);
|
|
14905
15533
|
if (removed.exitCode !== 0) {
|
|
14906
15534
|
result.warnings.push(
|
|
14907
|
-
`Could not replace the \`${name2}\` PriorityClass (value ${existingValue}, needs ${
|
|
15535
|
+
`Could not replace the \`${name2}\` PriorityClass (value ${existingValue}, needs ${wanted}): ${removed.stderr.trim().slice(0, 160)}`
|
|
14908
15536
|
);
|
|
14909
|
-
return;
|
|
15537
|
+
return "kept";
|
|
14910
15538
|
}
|
|
15539
|
+
return "removed";
|
|
15540
|
+
}
|
|
15541
|
+
async function applyRbac(ctx, desired, result) {
|
|
15542
|
+
const outcome = await reconcilePriorityClass(ctx, desired, result);
|
|
15543
|
+
const manifest = outcome === "kept" ? withoutKind(desired.rbacManifest, "PriorityClass") : desired.rbacManifest;
|
|
14911
15544
|
try {
|
|
14912
|
-
await kubectlApply(ctx.exec,
|
|
14913
|
-
result.recreatedPriorityClass = true;
|
|
15545
|
+
await kubectlApply(ctx.exec, manifest);
|
|
14914
15546
|
} catch (error) {
|
|
15547
|
+
if (outcome !== "removed") throw error;
|
|
15548
|
+
const value = await livePriorityClassValue(ctx, desired.priorityClass.name).catch(() => null);
|
|
15549
|
+
if (value === desired.priorityClass.value) {
|
|
15550
|
+
result.recreatedPriorityClass = true;
|
|
15551
|
+
throw error;
|
|
15552
|
+
}
|
|
14915
15553
|
result.warnings.push(
|
|
14916
|
-
`The \`${
|
|
15554
|
+
`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
15555
|
);
|
|
15556
|
+
return;
|
|
14918
15557
|
}
|
|
15558
|
+
if (outcome === "removed") result.recreatedPriorityClass = true;
|
|
14919
15559
|
}
|
|
14920
15560
|
async function step(result, label2, run) {
|
|
14921
15561
|
try {
|
|
@@ -14941,12 +15581,7 @@ async function reconcile(ctx, desired) {
|
|
|
14941
15581
|
`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
15582
|
);
|
|
14943
15583
|
} 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
|
-
);
|
|
15584
|
+
await step(result, "Could not apply RBAC", () => applyRbac(ctx, desired, result));
|
|
14950
15585
|
}
|
|
14951
15586
|
await step(
|
|
14952
15587
|
result,
|
|
@@ -14978,11 +15613,23 @@ async function reconcile(ctx, desired) {
|
|
|
14978
15613
|
"Could not apply kubelet settings",
|
|
14979
15614
|
() => reconcileKubelet(ctx, desired, result, managed)
|
|
14980
15615
|
);
|
|
15616
|
+
if (desired.isolation !== void 0) {
|
|
15617
|
+
await step(result, "Could not cap pod CPU", async () => {
|
|
15618
|
+
result.cpuQuotaApplied = await reconcilePodCpuQuota(ctx.exec, desired, result.warnings);
|
|
15619
|
+
});
|
|
15620
|
+
if (desired.kubelet.systemReserved?.cpu && result.cpuQuotaApplied === void 0) {
|
|
15621
|
+
result.cpuQuotaApplied = false;
|
|
15622
|
+
}
|
|
15623
|
+
}
|
|
14981
15624
|
if (registry && !result.restartedK3s) {
|
|
14982
15625
|
await step(result, "Could not load the local image registry mirror", async () => {
|
|
14983
15626
|
if (!managed) {
|
|
14984
15627
|
if (await registryMirrorPending(ctx.fs))
|
|
14985
|
-
result.warnings.push(
|
|
15628
|
+
result.warnings.push(
|
|
15629
|
+
ownerRestartWarning(
|
|
15630
|
+
"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."
|
|
15631
|
+
)
|
|
15632
|
+
);
|
|
14986
15633
|
return;
|
|
14987
15634
|
}
|
|
14988
15635
|
const loaded = await loadRegistryMirror(ctx);
|
|
@@ -15039,6 +15686,7 @@ async function enrollMachine(ports, env, cloudflaredBin, recorded = {}) {
|
|
|
15039
15686
|
const enrollment = await connection.enroll();
|
|
15040
15687
|
log2(`Enrolled as ${enrollment.hostname}.`);
|
|
15041
15688
|
await installUnits(ports, enrollment, cloudflaredBin, enrollment.desiredState.maintenance.hour);
|
|
15689
|
+
await ensureServiceAccount(ports);
|
|
15042
15690
|
const runningConveyorPods = await countRunningConveyorPods(exec);
|
|
15043
15691
|
const result = await reconcile({ exec, fs: fs2, runningConveyorPods }, enrollment.desiredState);
|
|
15044
15692
|
for (const warning of result.warnings) log2(warning);
|
|
@@ -15060,30 +15708,6 @@ async function enrollMachine(ports, env, cloudflaredBin, recorded = {}) {
|
|
|
15060
15708
|
for (const line of renderRegistryAuthSummary(missing)) log2(line);
|
|
15061
15709
|
}
|
|
15062
15710
|
|
|
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
15711
|
// src/lib/guest-vm.ts
|
|
15088
15712
|
async function requireGuestVm(exec) {
|
|
15089
15713
|
try {
|
|
@@ -15171,6 +15795,8 @@ var ClusterDaemon = class {
|
|
|
15171
15795
|
* against k3s's own settings before reporting it.
|
|
15172
15796
|
*/
|
|
15173
15797
|
isolation = void 0;
|
|
15798
|
+
/** Whether the pod slice's CPU cap is in force, from the last reconcile. */
|
|
15799
|
+
cpuQuotaApplied = void 0;
|
|
15174
15800
|
timers = [];
|
|
15175
15801
|
/** When the last store walk started, so one that failed is not retried on every reconcile. */
|
|
15176
15802
|
storeWalkStartedAt = 0;
|
|
@@ -15253,6 +15879,7 @@ var ClusterDaemon = class {
|
|
|
15253
15879
|
);
|
|
15254
15880
|
this.warnings = result.warnings;
|
|
15255
15881
|
this.isolation = result.isolation;
|
|
15882
|
+
this.cpuQuotaApplied = result.cpuQuotaApplied;
|
|
15256
15883
|
this.scheduleIsolationRetry();
|
|
15257
15884
|
for (const warning of result.warnings) this.ports.log(warning);
|
|
15258
15885
|
if (result.prunedNamespaces.length > 0) {
|
|
@@ -15409,6 +16036,11 @@ var ClusterDaemon = class {
|
|
|
15409
16036
|
);
|
|
15410
16037
|
}
|
|
15411
16038
|
for (const failure of result.failures) this.ports.log(`Image prune failed \u2014 ${failure}`);
|
|
16039
|
+
if (result.pending.length > 0) {
|
|
16040
|
+
this.ports.log(
|
|
16041
|
+
`Image removal still in progress for ${result.pending.length} image(s); the next prune re-checks them.`
|
|
16042
|
+
);
|
|
16043
|
+
}
|
|
15412
16044
|
} catch (error) {
|
|
15413
16045
|
this.ports.log(
|
|
15414
16046
|
`Skipping image prune: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -15447,6 +16079,7 @@ var ClusterDaemon = class {
|
|
|
15447
16079
|
localImageHashes: this.localImageHashes,
|
|
15448
16080
|
...registryOn ? { localRegistry: this.registryWatch } : {},
|
|
15449
16081
|
...isolation.isolation ? { isolation: isolation.isolation } : {},
|
|
16082
|
+
cpuQuotaApplied: this.cpuQuotaApplied,
|
|
15450
16083
|
k3sStoreBytes: this.measuredStoreBytes
|
|
15451
16084
|
},
|
|
15452
16085
|
inventory
|
|
@@ -15643,7 +16276,7 @@ var log = (line) => {
|
|
|
15643
16276
|
function resolveVersion() {
|
|
15644
16277
|
try {
|
|
15645
16278
|
const url = new URL("../package.json", import.meta.url);
|
|
15646
|
-
const pkg = JSON.parse(
|
|
16279
|
+
const pkg = JSON.parse(readFileSync6(url, "utf8"));
|
|
15647
16280
|
return pkg.version ?? "0.0.0";
|
|
15648
16281
|
} catch {
|
|
15649
16282
|
return "0.0.0";
|
|
@@ -15662,7 +16295,7 @@ function setupPorts(exec) {
|
|
|
15662
16295
|
async function runSetup(exec) {
|
|
15663
16296
|
await requireRoot(exec);
|
|
15664
16297
|
await requireGuestVm(exec);
|
|
15665
|
-
const env = readCliEnv();
|
|
16298
|
+
const env = readCliEnv(process.env, null);
|
|
15666
16299
|
const ports = setupPorts(exec);
|
|
15667
16300
|
const { cloudflaredBin } = await prepareMachine(ports);
|
|
15668
16301
|
await enrollMachine(ports, env, cloudflaredBin);
|
|
@@ -15672,15 +16305,23 @@ async function runHostSetup(exec) {
|
|
|
15672
16305
|
const { cloudflaredBin } = await prepareMachine(ports);
|
|
15673
16306
|
let env;
|
|
15674
16307
|
try {
|
|
15675
|
-
env = readCliEnv();
|
|
16308
|
+
env = readCliEnv(process.env, null);
|
|
15676
16309
|
} catch {
|
|
15677
16310
|
const state = await readState(nodeFileSystem);
|
|
15678
16311
|
log(
|
|
15679
16312
|
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
16313
|
);
|
|
16314
|
+
await reportCliInstall(exec);
|
|
15681
16315
|
return;
|
|
15682
16316
|
}
|
|
15683
16317
|
await enrollMachine(ports, env, cloudflaredBin, { runtime: "host" });
|
|
16318
|
+
await reportCliInstall(exec);
|
|
16319
|
+
}
|
|
16320
|
+
async function reportCliInstall(exec) {
|
|
16321
|
+
const linked = await linkCliOnPath(exec);
|
|
16322
|
+
if (linked.problem) log(linked.problem);
|
|
16323
|
+
const hint = await npmCopyHint((path) => nodeFileSystem.readFile(path));
|
|
16324
|
+
if (hint) log(hint);
|
|
15684
16325
|
}
|
|
15685
16326
|
function hostPorts(exec) {
|
|
15686
16327
|
return {
|
|
@@ -15755,6 +16396,8 @@ async function runStatus(exec, asJson, notEnrolledHint = "conveyor-k3 setup") {
|
|
|
15755
16396
|
}
|
|
15756
16397
|
const k3s = await exec("k3s", ["--version"]).catch(() => null);
|
|
15757
16398
|
const version = k3s ? parseK3sVersion(k3s.stdout || k3s.stderr) : null;
|
|
16399
|
+
const bins = await resolveBinaries(exec, state, process.argv[1]);
|
|
16400
|
+
const cloudflaredUpdates = await cloudflaredUpdater(exec, bins.cloudflared);
|
|
15758
16401
|
const report = {
|
|
15759
16402
|
cliVersion: resolveVersion(),
|
|
15760
16403
|
hostname: state.hostname,
|
|
@@ -15765,6 +16408,7 @@ async function runStatus(exec, asJson, notEnrolledHint = "conveyor-k3 setup") {
|
|
|
15765
16408
|
lastWarmAt: state.lastWarmAt,
|
|
15766
16409
|
lastPruneAt: state.lastPruneAt,
|
|
15767
16410
|
runningConveyorPods: await countRunningConveyorPods(exec),
|
|
16411
|
+
cloudflaredUpdates,
|
|
15768
16412
|
...state.runtime ? { runtime: state.runtime } : {}
|
|
15769
16413
|
};
|
|
15770
16414
|
if (asJson) {
|
|
@@ -15778,6 +16422,7 @@ async function runStatus(exec, asJson, notEnrolledHint = "conveyor-k3 setup") {
|
|
|
15778
16422
|
if (state.runtime) log(`runtime: ${state.runtime}`);
|
|
15779
16423
|
log(`k3s: ${report.k3sVersion ?? "not detected"}`);
|
|
15780
16424
|
for (const [unit, active] of Object.entries(units)) log(`${unit}: ${active}`);
|
|
16425
|
+
log(describeCloudflaredUpdater(cloudflaredUpdates));
|
|
15781
16426
|
log(`Settings version applied: ${report.appliedSettingsVersion}`);
|
|
15782
16427
|
log(`Conveyor pods running: ${report.runningConveyorPods}`);
|
|
15783
16428
|
}
|