@rallycry/conveyor-k3 0.1.3
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.d.ts +1 -0
- package/dist/cli.js +10739 -0
- package/dist/index.d.ts +777 -0
- package/dist/index.js +8900 -0
- package/dist/vm-qemu.d.ts +1 -0
- package/dist/vm-qemu.js +1182 -0
- package/package.json +46 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import { PersonalClusterEnrollmentBundle, PersonalClusterEnrollmentCompletion, PersonalClusterDesiredState, PersonalClusterHeartbeat, PersonalClusterKubeletState } from '@project/shared';
|
|
2
|
+
|
|
3
|
+
interface ClusterConnectionConfig {
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
clusterToken: string;
|
|
6
|
+
/** Test-only override; production always reads the root-owned guest key. */
|
|
7
|
+
deviceKeyPath?: string;
|
|
8
|
+
}
|
|
9
|
+
declare class ClusterConnection {
|
|
10
|
+
private socket;
|
|
11
|
+
private readonly config;
|
|
12
|
+
/** Called when Conveyor says the desired state changed. */
|
|
13
|
+
onSyncRequested: (() => void) | null;
|
|
14
|
+
/** Called when Conveyor asks the machine to remove itself. */
|
|
15
|
+
onUninstall: (() => Promise<void>) | null;
|
|
16
|
+
constructor(config: ClusterConnectionConfig);
|
|
17
|
+
connect(): Promise<void>;
|
|
18
|
+
private require;
|
|
19
|
+
enroll(): Promise<PersonalClusterEnrollmentBundle>;
|
|
20
|
+
completeEnrollment(payload: PersonalClusterEnrollmentCompletion): Promise<void>;
|
|
21
|
+
requestDesiredState(): Promise<PersonalClusterDesiredState>;
|
|
22
|
+
/** Fire-and-forget: a lost heartbeat is covered by the next one. */
|
|
23
|
+
sendHeartbeat(payload: PersonalClusterHeartbeat): void;
|
|
24
|
+
/**
|
|
25
|
+
* Tell Conveyor this machine is stopping, so placement skips it at once
|
|
26
|
+
* instead of waiting out the stale window.
|
|
27
|
+
*/
|
|
28
|
+
sayGoodbye(timeoutMs?: number): Promise<void>;
|
|
29
|
+
disconnect(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface RunResult {
|
|
33
|
+
stdout: string;
|
|
34
|
+
stderr: string;
|
|
35
|
+
exitCode: number;
|
|
36
|
+
}
|
|
37
|
+
interface RunOptions {
|
|
38
|
+
/** Hard cap on runtime. The process is SIGTERMed on timeout. */
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
/** Bound output from untrusted guests before it can exhaust host memory. */
|
|
41
|
+
maxOutputBytes?: number;
|
|
42
|
+
/** Isolate and terminate the whole command tree for guest SSH commands. */
|
|
43
|
+
killProcessGroup?: boolean;
|
|
44
|
+
/** Piped to the child's stdin, for `--password-stdin` style input. */
|
|
45
|
+
stdin?: string;
|
|
46
|
+
env?: NodeJS.ProcessEnv;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runs a command and returns its result — including a NON-ZERO exit.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately not throwing. Almost every caller here has a meaningful
|
|
52
|
+
* response to a failure (defer a restart, record a warning, try the next
|
|
53
|
+
* image) and exceptions would turn each of those into a try/catch. The two
|
|
54
|
+
* places that genuinely require success use {@link runOrThrow}.
|
|
55
|
+
*/
|
|
56
|
+
type Executor = (bin: string, args: readonly string[], options?: RunOptions) => Promise<RunResult>;
|
|
57
|
+
declare const spawnExecutor: Executor;
|
|
58
|
+
declare class CommandError extends Error {
|
|
59
|
+
readonly exitCode: number;
|
|
60
|
+
readonly stdout: string;
|
|
61
|
+
readonly stderr: string;
|
|
62
|
+
constructor(bin: string, result: RunResult);
|
|
63
|
+
}
|
|
64
|
+
/** Run a command and throw {@link CommandError} unless it exits 0. */
|
|
65
|
+
declare function runOrThrow(exec: Executor, bin: string, args: readonly string[], options?: RunOptions): Promise<RunResult>;
|
|
66
|
+
|
|
67
|
+
interface FileSystemPort$1 {
|
|
68
|
+
readFile(path: string): Promise<string | null>;
|
|
69
|
+
writeFile(path: string, content: string, mode?: number): Promise<void>;
|
|
70
|
+
removeFile(path: string): Promise<void>;
|
|
71
|
+
ensureDir(path: string): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
declare const nodeFileSystem: FileSystemPort$1;
|
|
74
|
+
/** In-memory filesystem for tests. Exported so suites share one shape. */
|
|
75
|
+
declare function createMemoryFileSystem(seed?: Record<string, string>): FileSystemPort$1 & {
|
|
76
|
+
files: Map<string, string>;
|
|
77
|
+
modes: Map<string, number>;
|
|
78
|
+
removed: string[];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
interface DaemonPorts {
|
|
82
|
+
exec: Executor;
|
|
83
|
+
fs: FileSystemPort$1;
|
|
84
|
+
connection: ClusterConnection;
|
|
85
|
+
cliVersion: string;
|
|
86
|
+
log: (line: string) => void;
|
|
87
|
+
}
|
|
88
|
+
declare class ClusterDaemon {
|
|
89
|
+
private readonly ports;
|
|
90
|
+
private desired;
|
|
91
|
+
private state;
|
|
92
|
+
private warnings;
|
|
93
|
+
/**
|
|
94
|
+
* The image-GC high threshold the kubelet is really using — ours, or the
|
|
95
|
+
* owner's when their own config.yaml set it and the drop-in stood down.
|
|
96
|
+
* Null until the first reconcile has read both.
|
|
97
|
+
*/
|
|
98
|
+
private effectiveImageGcHigh;
|
|
99
|
+
/**
|
|
100
|
+
* Warnings the last warm produced, as opposed to the last reconcile.
|
|
101
|
+
*
|
|
102
|
+
* Kept separate because the two clocks are separate: a warm runs hourly and
|
|
103
|
+
* a reconcile daily, and folding warm's findings into `warnings` would have
|
|
104
|
+
* the next reconcile silently clear them.
|
|
105
|
+
*/
|
|
106
|
+
private warmWarnings;
|
|
107
|
+
private timers;
|
|
108
|
+
/** Serializes reconcile against itself — a sync nudge during a reconcile
|
|
109
|
+
* must queue, not interleave two kubectl applies of the same manifests. */
|
|
110
|
+
private reconciling;
|
|
111
|
+
constructor(ports: DaemonPorts);
|
|
112
|
+
start(): Promise<void>;
|
|
113
|
+
stop(): Promise<void>;
|
|
114
|
+
/** Fetch the desired state and converge to it. Queued, never concurrent. */
|
|
115
|
+
syncAndReconcile(): Promise<void>;
|
|
116
|
+
private runReconcile;
|
|
117
|
+
/**
|
|
118
|
+
* The hourly clock: warm, re-pin, and reclaim if the pile justifies it.
|
|
119
|
+
*
|
|
120
|
+
* The prune check rides the hourly warm rather than the daily reconcile
|
|
121
|
+
* because that is the cadence a bake-heavy day actually needs. With the
|
|
122
|
+
* kubelet back on its own thresholds, nothing else clears stranded
|
|
123
|
+
* generations in between.
|
|
124
|
+
*/
|
|
125
|
+
private warmAndPruneIfDue;
|
|
126
|
+
/** Pre-pull the wanted refs. Silent on success; that is the normal case. */
|
|
127
|
+
warm(): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Take the wanted refs out of the kubelet's reach, and release the ones that
|
|
130
|
+
* have left the wanted set.
|
|
131
|
+
*
|
|
132
|
+
* Runs on every warm, not once: importing an image under the same ref
|
|
133
|
+
* replaces the containerd record and drops the label with it, so a bake
|
|
134
|
+
* always leaves its `:current` unpinned until the next pass. It pins from
|
|
135
|
+
* the INVENTORY rather than from what warm pulled, because `warmImages`
|
|
136
|
+
* skips `localhost/` refs — which are the ones a local bake produces, and
|
|
137
|
+
* the ones that were actually being deleted.
|
|
138
|
+
*/
|
|
139
|
+
private pinWanted;
|
|
140
|
+
/** Prune daily, or sooner when the disk is tight. */
|
|
141
|
+
private pruneIfDue;
|
|
142
|
+
/**
|
|
143
|
+
* Whether stranded generations alone justify a prune now.
|
|
144
|
+
*
|
|
145
|
+
* A bake-heavy day strands several gigabytes per bake, and with the kubelet
|
|
146
|
+
* back on its own thresholds nothing else clears them before the daily run.
|
|
147
|
+
* An unreadable inventory answers false: the prune itself would refuse the
|
|
148
|
+
* cycle anyway, and guessing "yes" would just run it to find that out.
|
|
149
|
+
*/
|
|
150
|
+
private strandedBytesExceedLimit;
|
|
151
|
+
prune(): Promise<void>;
|
|
152
|
+
private buildHeartbeat;
|
|
153
|
+
beat(): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Converge the machine onto Conveyor's desired state.
|
|
158
|
+
*
|
|
159
|
+
* This is what "manages itself" means in code. Conveyor publishes what the
|
|
160
|
+
* cluster SHOULD look like; this function makes it so, and it runs on connect,
|
|
161
|
+
* on request, and daily. Nothing here is a command from the server — a machine
|
|
162
|
+
* that was powered off for a week catches up on its next run, and a practice
|
|
163
|
+
* Conveyor changes reaches every enrolled box without anyone touching one.
|
|
164
|
+
*
|
|
165
|
+
* Three properties every step keeps:
|
|
166
|
+
*
|
|
167
|
+
* 1. **Idempotent.** Running twice changes nothing the first run already did.
|
|
168
|
+
* 2. **Independently fallible.** A step that fails records a warning and the
|
|
169
|
+
* rest still run. A cluster with one bad namespace is not a reason to skip
|
|
170
|
+
* image hygiene.
|
|
171
|
+
* 3. **Additive.** Nothing here deletes a namespace, a workload, or an image
|
|
172
|
+
* that is in use. The machine belongs to its owner.
|
|
173
|
+
* 4. **Bounded.** The server does not get to say what runs here. The one field
|
|
174
|
+
* it sends as raw YAML is checked against a local kind allowlist before it
|
|
175
|
+
* reaches `kubectl` — see `../lib/manifest-guard.js`.
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
/** The filesystem operations reconcile needs, injectable for tests. */
|
|
179
|
+
interface FileSystemPort {
|
|
180
|
+
readFile(path: string): Promise<string | null>;
|
|
181
|
+
writeFile(path: string, content: string, mode?: number): Promise<void>;
|
|
182
|
+
removeFile(path: string): Promise<void>;
|
|
183
|
+
ensureDir(path: string): Promise<void>;
|
|
184
|
+
}
|
|
185
|
+
interface ReconcileContext {
|
|
186
|
+
exec: Executor;
|
|
187
|
+
fs: FileSystemPort;
|
|
188
|
+
/**
|
|
189
|
+
* Conveyor pods currently running here, or null when that could not be
|
|
190
|
+
* determined. A disruptive step is unsafe above zero AND unsafe on null —
|
|
191
|
+
* "I could not ask" is not "nothing is running".
|
|
192
|
+
*/
|
|
193
|
+
runningConveyorPods: number | null;
|
|
194
|
+
}
|
|
195
|
+
interface ReconcileResult {
|
|
196
|
+
appliedVersion: number;
|
|
197
|
+
warnings: string[];
|
|
198
|
+
/** True when the kubelet drop-in changed and k3s was restarted. */
|
|
199
|
+
restartedK3s: boolean;
|
|
200
|
+
/** Kubelet flags left to the owner because their own config sets them. */
|
|
201
|
+
skippedKubeletFlags: string[];
|
|
202
|
+
/**
|
|
203
|
+
* The image-GC high threshold the kubelet is actually enforcing — ours, or
|
|
204
|
+
* the owner's when their own config.yaml set it and the drop-in stood down.
|
|
205
|
+
* Null when neither side named one. Reported so the heartbeat can warn about
|
|
206
|
+
* GC pressure against the number in force rather than the one we sent.
|
|
207
|
+
*/
|
|
208
|
+
effectiveImageGcHighThreshold: number | null;
|
|
209
|
+
/** True when a wrong-valued PriorityClass was deleted AND put back. */
|
|
210
|
+
recreatedPriorityClass: boolean;
|
|
211
|
+
}
|
|
212
|
+
declare function reconcile(ctx: ReconcileContext, desired: PersonalClusterDesiredState): Promise<ReconcileResult>;
|
|
213
|
+
|
|
214
|
+
/** One entry of `crictl images -o json`. */
|
|
215
|
+
interface CrictlImage {
|
|
216
|
+
id: string;
|
|
217
|
+
repoTags?: string[];
|
|
218
|
+
repoDigests?: string[];
|
|
219
|
+
size?: string;
|
|
220
|
+
/** CRI reports this for a pinned image; absent on a runtime too old to say. */
|
|
221
|
+
pinned?: boolean;
|
|
222
|
+
}
|
|
223
|
+
interface ImageInventory {
|
|
224
|
+
images: CrictlImage[];
|
|
225
|
+
/** Image ids referenced by a container, running or not. */
|
|
226
|
+
referencedIds: Set<string>;
|
|
227
|
+
}
|
|
228
|
+
interface PruneCandidate {
|
|
229
|
+
id: string;
|
|
230
|
+
sizeBytes: number;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Which images may be removed.
|
|
234
|
+
*
|
|
235
|
+
* Pure, so the selection table is testable against a real `crictl images`
|
|
236
|
+
* fixture rather than inferred from what the daemon happened to delete.
|
|
237
|
+
*/
|
|
238
|
+
declare function selectStrandedImages(inventory: ImageInventory, wantedImages: string[]): PruneCandidate[];
|
|
239
|
+
/** Read the machine's images and which of them containers are holding. */
|
|
240
|
+
declare function readImageInventory(exec: Executor): Promise<ImageInventory>;
|
|
241
|
+
interface PruneResult {
|
|
242
|
+
removed: number;
|
|
243
|
+
bytesReclaimed: number;
|
|
244
|
+
failures: string[];
|
|
245
|
+
}
|
|
246
|
+
/** Remove the stranded images. Individual failures are reported, not thrown. */
|
|
247
|
+
declare function pruneStrandedImages(exec: Executor, inventory: ImageInventory, wantedImages: string[]): Promise<PruneResult>;
|
|
248
|
+
interface WarmResult {
|
|
249
|
+
pulled: string[];
|
|
250
|
+
skipped: string[];
|
|
251
|
+
failures: string[];
|
|
252
|
+
/** How many refs were skipped for being on Artifact Registry. */
|
|
253
|
+
skippedArtifactRegistry: number;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Pre-pull the refs a build will ask for.
|
|
257
|
+
*
|
|
258
|
+
* A no-op download when the ref has not moved, and the difference between a
|
|
259
|
+
* card starting immediately and a card waiting on a multi-gigabyte cold pull
|
|
260
|
+
* right after a bake.
|
|
261
|
+
*
|
|
262
|
+
* Three kinds of ref are skipped rather than attempted:
|
|
263
|
+
* - `localhost/...`, which a local bake already imported into containerd and
|
|
264
|
+
* which no registry can serve.
|
|
265
|
+
* - anything on a host we know has no credential, because that pull would
|
|
266
|
+
* fail slowly and noisily every hour.
|
|
267
|
+
* - Artifact Registry, whatever the credential. A personal cluster sits
|
|
268
|
+
* outside GCP, so that pull crosses the internet and is billed as AR egress
|
|
269
|
+
* — and such a ref is never placed here, so it buys nothing. The server
|
|
270
|
+
* already excludes these from `warmImages`, but a daemon can be running a
|
|
271
|
+
* desired state older than that field, so the refusal lives here too.
|
|
272
|
+
*/
|
|
273
|
+
declare function warmImages(exec: Executor, wantedImages: string[], unauthenticatedHosts: string[]): Promise<WarmResult>;
|
|
274
|
+
|
|
275
|
+
interface TelemetryState {
|
|
276
|
+
cliVersion: string;
|
|
277
|
+
appliedDesiredStateVersion: number;
|
|
278
|
+
wantedImages: string[];
|
|
279
|
+
authRequiredHosts: string[];
|
|
280
|
+
lastPruneAt: string | null;
|
|
281
|
+
lastWarmAt: string | null;
|
|
282
|
+
lastUpdateAt: string | null;
|
|
283
|
+
localWarnings: string[];
|
|
284
|
+
/**
|
|
285
|
+
* The image-GC high threshold the kubelet is really enforcing, or null when
|
|
286
|
+
* no reconcile has read one yet. Drives the GC-pressure warning below.
|
|
287
|
+
*/
|
|
288
|
+
imageGcHighThreshold?: number | null;
|
|
289
|
+
}
|
|
290
|
+
interface TelemetryPorts {
|
|
291
|
+
exec: Executor;
|
|
292
|
+
readFile(path: string): Promise<string | null>;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Conveyor pods running here, across every namespace it manages — or null when
|
|
296
|
+
* that cannot be determined.
|
|
297
|
+
*
|
|
298
|
+
* Three destructive operations gate on this number: the k3s restart after a
|
|
299
|
+
* kubelet change, the k3s channel upgrade, and the PriorityClass recreate. So
|
|
300
|
+
* "I could not ask" must NOT collapse to "nothing is running" — that reads as
|
|
301
|
+
* an idle machine and takes a card down mid-build. It returns null instead, and
|
|
302
|
+
* every guard treats null as busy.
|
|
303
|
+
*
|
|
304
|
+
* The selector comes from `@project/shared` because the API stamps the same
|
|
305
|
+
* label on the pods it creates. A hand-written selector here silently matched
|
|
306
|
+
* nothing and disabled all three guards at once.
|
|
307
|
+
*/
|
|
308
|
+
declare function countRunningConveyorPods(exec: Executor): Promise<number | null>;
|
|
309
|
+
declare function collectHeartbeat(ports: TelemetryPorts, state: TelemetryState, inventory: ImageInventory | null): Promise<PersonalClusterHeartbeat>;
|
|
310
|
+
|
|
311
|
+
interface RegistryAuth {
|
|
312
|
+
username?: string;
|
|
313
|
+
password?: string;
|
|
314
|
+
auth?: string;
|
|
315
|
+
identitytoken?: string;
|
|
316
|
+
}
|
|
317
|
+
interface RegistryConfigEntry {
|
|
318
|
+
auth?: RegistryAuth;
|
|
319
|
+
tls?: Record<string, unknown>;
|
|
320
|
+
}
|
|
321
|
+
interface RegistriesFile {
|
|
322
|
+
mirrors?: Record<string, unknown>;
|
|
323
|
+
configs?: Record<string, RegistryConfigEntry>;
|
|
324
|
+
[key: string]: unknown;
|
|
325
|
+
}
|
|
326
|
+
/** Parse the file. An absent or empty file is an empty config, not an error. */
|
|
327
|
+
declare function parseRegistries(raw: string | null): RegistriesFile;
|
|
328
|
+
/** Hosts that already carry a credential. */
|
|
329
|
+
declare function configuredAuthHosts(file: RegistriesFile): string[];
|
|
330
|
+
/**
|
|
331
|
+
* Required hosts with no credential configured.
|
|
332
|
+
*
|
|
333
|
+
* This is what the daemon reports in its heartbeat and what the profile turns
|
|
334
|
+
* into a `conveyor-k3 registry add <host>` line. Without it, the first
|
|
335
|
+
* acceptance run stalls on an unauthenticated multi-gigabyte pull with nothing
|
|
336
|
+
* saying why.
|
|
337
|
+
*/
|
|
338
|
+
declare function missingAuthHosts(file: RegistriesFile, requiredHosts: string[]): string[];
|
|
339
|
+
/**
|
|
340
|
+
* Insert or replace ONE host's auth entry, preserving everything else.
|
|
341
|
+
*
|
|
342
|
+
* Everything outside `configs.<host>.auth` is carried through untouched:
|
|
343
|
+
* other hosts, the `mirrors` block, and the target host's own `tls` settings.
|
|
344
|
+
*/
|
|
345
|
+
declare function upsertRegistryAuth(file: RegistriesFile, host: string, auth: RegistryAuth): RegistriesFile;
|
|
346
|
+
/** Serialize back to YAML. */
|
|
347
|
+
declare function serializeRegistries(file: RegistriesFile): string;
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* The k3s configuration Conveyor sets, as a drop-in.
|
|
351
|
+
*
|
|
352
|
+
* k3s reads `/etc/rancher/k3s/config.yaml` and then every `*.yaml` under
|
|
353
|
+
* `config.yaml.d/`, merging them. Our settings go in the drop-in so the
|
|
354
|
+
* owner's `config.yaml` is never rewritten — this machine is theirs, and a
|
|
355
|
+
* managed cluster that silently replaced their config would be worse than no
|
|
356
|
+
* managed mode at all.
|
|
357
|
+
*
|
|
358
|
+
* Two details make the merge safe:
|
|
359
|
+
*
|
|
360
|
+
* - `kubelet-arg+:` APPENDS to any `kubelet-arg` list the owner already has,
|
|
361
|
+
* where a bare `kubelet-arg:` would replace it.
|
|
362
|
+
* - A GC threshold the owner already sets in their own `config.yaml` is
|
|
363
|
+
* SKIPPED, not overridden. They chose a number for a reason, and duplicate
|
|
364
|
+
* kubelet flags are a startup failure, not a last-wins merge.
|
|
365
|
+
*/
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Bundled components Conveyor turns off, and the kubeconfig mode it sets.
|
|
369
|
+
*
|
|
370
|
+
* These were passed to the k3s INSTALLER at setup, which is where they stopped
|
|
371
|
+
* surviving: the installer rebuilds the unit's `ExecStart` from its own
|
|
372
|
+
* arguments on every run, so an upgrade that does not repeat them rewrites the
|
|
373
|
+
* unit to a bare `k3s server` and traefik and servicelb come back — claiming
|
|
374
|
+
* host ports 80 and 443 on a machine its owner uses for other things.
|
|
375
|
+
*
|
|
376
|
+
* k3s merges the drop-in on every start regardless of how the unit was
|
|
377
|
+
* written, so carrying them here makes them survive an upgrade by construction
|
|
378
|
+
* rather than by remembering to pass them again. `update.ts` passes the same
|
|
379
|
+
* exec string as well, so the unit stays identical too — belt and braces.
|
|
380
|
+
*/
|
|
381
|
+
declare const K3S_DISABLED_COMPONENTS: readonly ["traefik", "servicelb"];
|
|
382
|
+
/**
|
|
383
|
+
* What `setup` and `update` both pass as `INSTALL_K3S_EXEC`.
|
|
384
|
+
*
|
|
385
|
+
* One constant because the installer has no "keep what is there" mode: an
|
|
386
|
+
* unset value means a bare `k3s server`, so the two call sites drifting apart
|
|
387
|
+
* is exactly the bug this fixes.
|
|
388
|
+
*/
|
|
389
|
+
declare const K3S_INSTALL_EXEC: string;
|
|
390
|
+
interface DropInRenderResult {
|
|
391
|
+
/** The file to write, or null when the owner already set everything. */
|
|
392
|
+
content: string | null;
|
|
393
|
+
/** Flags skipped because the owner's own config already sets them. */
|
|
394
|
+
skipped: string[];
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Which kubelet flags the owner's `config.yaml` already sets.
|
|
398
|
+
*
|
|
399
|
+
* A deliberately textual scan rather than a YAML parse: we only need to know
|
|
400
|
+
* whether a flag NAME appears under a `kubelet-arg` key, and a config that
|
|
401
|
+
* fails to parse must not stop us reporting what we can see. Over-detecting
|
|
402
|
+
* costs a skipped flag and a note; under-detecting costs a kubelet that will
|
|
403
|
+
* not start.
|
|
404
|
+
*/
|
|
405
|
+
declare function ownerConfiguredKubeletFlags(ownerConfig: string | null): Set<string>;
|
|
406
|
+
/**
|
|
407
|
+
* Whether the owner's `config.yaml` sets a top-level key of its own.
|
|
408
|
+
*
|
|
409
|
+
* Same textual-scan reasoning as the kubelet flags above: a top-level key is
|
|
410
|
+
* `key:` at column zero, and a config that fails to parse must not stop us
|
|
411
|
+
* reporting what we can see.
|
|
412
|
+
*/
|
|
413
|
+
declare function ownerConfiguredKeys(ownerConfig: string | null): Set<string>;
|
|
414
|
+
/**
|
|
415
|
+
* Render the drop-in: the kubelet settings, plus the components Conveyor turns
|
|
416
|
+
* off and the kubeconfig mode it sets.
|
|
417
|
+
*
|
|
418
|
+
* Returns `content: null` when there is nothing left for us to set, so the
|
|
419
|
+
* caller can remove a stale drop-in rather than leaving an empty one behind.
|
|
420
|
+
*
|
|
421
|
+
* `disable+:` APPENDS, so an owner who disables something of their own keeps
|
|
422
|
+
* it and gains ours. That is why the disables are not skipped the way a
|
|
423
|
+
* kubelet flag is: duplicate kubelet flags are a startup failure, whereas
|
|
424
|
+
* `disable` is a set of component names where a repeat is harmless — and
|
|
425
|
+
* standing down would quietly re-enable servicelb for an owner who had only
|
|
426
|
+
* meant to disable traefik.
|
|
427
|
+
*
|
|
428
|
+
* `write-kubeconfig-mode` IS skipped when the owner sets it: it is a scalar
|
|
429
|
+
* they chose, and last-writer-wins on their own file is exactly what this
|
|
430
|
+
* module exists not to do.
|
|
431
|
+
*/
|
|
432
|
+
declare function renderK3sDropIn(kubelet: PersonalClusterKubeletState, ownerConfig: string | null): DropInRenderResult;
|
|
433
|
+
/**
|
|
434
|
+
* @deprecated Use {@link renderK3sDropIn}. Kept as a named alias because the
|
|
435
|
+
* package exports it and a published CLI may still import it by this name.
|
|
436
|
+
*/
|
|
437
|
+
declare const renderKubeletDropIn: typeof renderK3sDropIn;
|
|
438
|
+
|
|
439
|
+
interface UnitRenderOptions {
|
|
440
|
+
/** Absolute path to the installed `conveyor-k3` binary. */
|
|
441
|
+
cliBin: string;
|
|
442
|
+
/** Absolute path to `cloudflared`. */
|
|
443
|
+
cloudflaredBin: string;
|
|
444
|
+
/** Local hour (0-23) the update timer fires in. */
|
|
445
|
+
maintenanceHour: number;
|
|
446
|
+
}
|
|
447
|
+
/** Every unit file this CLI writes, keyed by unit name. */
|
|
448
|
+
declare function renderAllUnits(options: UnitRenderOptions): Record<string, string>;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The machine's own record of what it has done.
|
|
452
|
+
*
|
|
453
|
+
* Small on purpose: everything authoritative lives on Conveyor's side, and
|
|
454
|
+
* this file exists so a daemon restart does not re-warm every image or forget
|
|
455
|
+
* which settings version it applied. Losing it costs one extra reconcile.
|
|
456
|
+
*/
|
|
457
|
+
|
|
458
|
+
interface MachineState {
|
|
459
|
+
/** The desired-state version last successfully applied. */
|
|
460
|
+
appliedDesiredStateVersion: number;
|
|
461
|
+
/** Tunnel hostname Conveyor routed here, for `status` to print. */
|
|
462
|
+
hostname: string | null;
|
|
463
|
+
/** True once the ServiceAccount token has been handed to Conveyor. */
|
|
464
|
+
enrolled: boolean;
|
|
465
|
+
lastPruneAt: string | null;
|
|
466
|
+
lastWarmAt: string | null;
|
|
467
|
+
lastUpdateAt: string | null;
|
|
468
|
+
/**
|
|
469
|
+
* Absolute paths setup resolved for the binaries the updater invokes.
|
|
470
|
+
*
|
|
471
|
+
* Recorded because systemd's default unit PATH does not include
|
|
472
|
+
* `/root/.bun/bin`, so a bare name is ENOENT from the update timer. Optional:
|
|
473
|
+
* a state file written before this field existed falls back to `which`.
|
|
474
|
+
*/
|
|
475
|
+
bins?: {
|
|
476
|
+
bun: string;
|
|
477
|
+
cli: string;
|
|
478
|
+
cloudflared: string;
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* Refs this daemon has labelled `io.cri-containerd.pinned`.
|
|
482
|
+
*
|
|
483
|
+
* Recorded so a ref that LEAVES the wanted set can be released again. The
|
|
484
|
+
* node's own pinned images — the pause sandbox, a co-tenant's — look
|
|
485
|
+
* identical to a demoted one from the inventory alone, so "pinned but not
|
|
486
|
+
* wanted" is not a safe rule to unpin on. Only what we pinned, we unpin.
|
|
487
|
+
*/
|
|
488
|
+
pinnedImages?: string[];
|
|
489
|
+
}
|
|
490
|
+
declare function readState(fs: FileSystemPort$1): Promise<MachineState>;
|
|
491
|
+
declare function writeState(fs: FileSystemPort$1, state: MachineState): Promise<void>;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* The verb table.
|
|
495
|
+
*
|
|
496
|
+
* One binary and one table, rather than the deployment runner's no-argv-at-all
|
|
497
|
+
* or conveyor-mcp's one-bin-per-verb. A machine's owner types these, so the
|
|
498
|
+
* help output below is part of the product: every verb states what it does to
|
|
499
|
+
* their machine, and `uninstall` states what it does not.
|
|
500
|
+
*/
|
|
501
|
+
declare const VERBS: readonly ["vm", "setup", "start", "stop", "status", "sync", "update", "registry", "runners", "uninstall", "daemon"];
|
|
502
|
+
type Verb = (typeof VERBS)[number];
|
|
503
|
+
declare function isVerb(value: string): value is Verb;
|
|
504
|
+
interface ParsedArgs {
|
|
505
|
+
verb: Verb | null;
|
|
506
|
+
/** Positional arguments after the verb (e.g. the registry host). */
|
|
507
|
+
positional: string[];
|
|
508
|
+
flags: Record<string, string | true>;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Parse `conveyor-k3 <verb> [positional…] [--flag[=value]]`.
|
|
512
|
+
*
|
|
513
|
+
* Deliberately small. A dependency-free parser is auditable at a glance, and
|
|
514
|
+
* this surface is nine verbs and six flags, not a general CLI framework.
|
|
515
|
+
*/
|
|
516
|
+
declare function parseArgs(argv: readonly string[]): ParsedArgs;
|
|
517
|
+
declare const HELP = "conveyor-k3 \u2014 managed personal compute for Conveyor\n\nUsage: conveyor-k3 <command> [options]\n\nCommands:\n vm Manage isolated compute on macOS or Linux.\n Run conveyor-k3 vm for host lifecycle commands.\n setup Install and enrol this machine. Safe to re-run.\n start Start the tunnel and the daemon.\n stop Stop both, and tell Conveyor at once so your next\n build goes straight to project compute.\n status [--json] What is running, what Conveyor knows, and what is\n missing.\n sync Re-read your settings from Conveyor and apply them.\n update Update conveyor-k3, cloudflared, and (when you have\n allowed it and nothing is running) k3s.\n registry list Which registry credentials are configured, and which\n ones your projects still need.\n registry add <host> Add one registry credential.\n --username <user>\n --password-stdin read the password from stdin\n --no-restart skip the required k3s restart\n runners list Which GitHub Actions runners this machine hosts, and\n whether each may import a locally baked image.\n runners grant <user>\u2026 Let those runner users import a locally baked image\n into this node and pin it. Writes one file under\n /etc/sudoers.d, checked with visudo first. Needed only\n when a project has Bake Locally turned on.\n uninstall Remove Conveyor's units, environment, and state. Your\n k3s config, registries.yaml, and workloads are left\n alone.\n --purge-k3s --yes also remove k3s itself\n daemon Run in the foreground (what the systemd unit calls).\n\nOptions:\n --version Print the version.\n --help Print this message.\n\nDocs: https://conveyor.rallycryapp.com/docs/guides/managed-personal-compute\n";
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* `conveyor-k3 registry add|list` — the one place this CLI writes
|
|
521
|
+
* `registries.yaml`.
|
|
522
|
+
*
|
|
523
|
+
* The reconcile loop never touches that file. Only an explicit command does,
|
|
524
|
+
* it takes a timestamped backup first, and it merges rather than templates —
|
|
525
|
+
* so a machine pulling through its own LAN mirror keeps that mirror, its TLS
|
|
526
|
+
* settings, and every other host it had configured.
|
|
527
|
+
*
|
|
528
|
+
* A restart is required and is not optional: k3s reads `registries.yaml` at
|
|
529
|
+
* startup only, so a credential added without one silently does nothing.
|
|
530
|
+
*/
|
|
531
|
+
|
|
532
|
+
interface RegistryPorts {
|
|
533
|
+
exec: Executor;
|
|
534
|
+
fs: FileSystemPort$1;
|
|
535
|
+
log: (line: string) => void;
|
|
536
|
+
/** Injected so the backup filename is deterministic in tests. */
|
|
537
|
+
now: () => Date;
|
|
538
|
+
}
|
|
539
|
+
interface RegistryAddOptions {
|
|
540
|
+
host: string;
|
|
541
|
+
username?: string;
|
|
542
|
+
password?: string;
|
|
543
|
+
/** Skip the k3s restart. The caller must then restart it themselves. */
|
|
544
|
+
noRestart?: boolean;
|
|
545
|
+
}
|
|
546
|
+
interface RegistryAddResult {
|
|
547
|
+
backupPath: string | null;
|
|
548
|
+
restarted: boolean;
|
|
549
|
+
}
|
|
550
|
+
declare function registryAdd(ports: RegistryPorts, options: RegistryAddOptions): Promise<RegistryAddResult>;
|
|
551
|
+
interface RegistryCoverage {
|
|
552
|
+
configured: string[];
|
|
553
|
+
missing: string[];
|
|
554
|
+
}
|
|
555
|
+
/** What is configured against what Conveyor says this machine needs. */
|
|
556
|
+
declare function registryList(ports: RegistryPorts, requiredHosts: string[]): Promise<RegistryCoverage>;
|
|
557
|
+
/**
|
|
558
|
+
* The lines `setup` prints at the very end.
|
|
559
|
+
*
|
|
560
|
+
* Without this the acceptance flow stalls on an unauthenticated multi-gigabyte
|
|
561
|
+
* pull with nothing saying why, so it is part of setup's contract rather than
|
|
562
|
+
* a nicety.
|
|
563
|
+
*/
|
|
564
|
+
declare function renderRegistryAuthSummary(missing: string[]): string[];
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* `conveyor-k3 uninstall` — remove exactly what Conveyor put on this machine.
|
|
568
|
+
*
|
|
569
|
+
* Everything this CLI created carries a `conveyor-k3` name and lives under
|
|
570
|
+
* `/etc/conveyor` or `/var/lib/conveyor-k3`, which is what makes a precise
|
|
571
|
+
* removal possible. Nothing else is touched: not the owner's `config.yaml`,
|
|
572
|
+
* not `registries.yaml`, not their workloads.
|
|
573
|
+
*
|
|
574
|
+
* k3s itself is NOT removed by default. On most machines it predates us, or
|
|
575
|
+
* the owner wants to keep it — so removing it is `--purge-k3s`, and even then
|
|
576
|
+
* only after an explicit confirmation.
|
|
577
|
+
*/
|
|
578
|
+
|
|
579
|
+
interface UninstallPorts {
|
|
580
|
+
exec: Executor;
|
|
581
|
+
fs: FileSystemPort$1;
|
|
582
|
+
log: (line: string) => void;
|
|
583
|
+
}
|
|
584
|
+
interface UninstallOptions {
|
|
585
|
+
/** Also run k3s-uninstall.sh. Requires `confirmed`. */
|
|
586
|
+
purgeK3s?: boolean;
|
|
587
|
+
/** The caller has an explicit yes from the person running this. */
|
|
588
|
+
confirmed?: boolean;
|
|
589
|
+
/** Conveyor asked for this, so the "click Disconnect" reminder is moot. */
|
|
590
|
+
serverInitiated?: boolean;
|
|
591
|
+
}
|
|
592
|
+
interface UninstallResult {
|
|
593
|
+
stoppedUnits: string[];
|
|
594
|
+
removedFiles: string[];
|
|
595
|
+
purgedK3s: boolean;
|
|
596
|
+
}
|
|
597
|
+
declare function uninstall(ports: UninstallPorts, options?: UninstallOptions): Promise<UninstallResult>;
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* `conveyor-k3 update` — the maintenance-window updater.
|
|
601
|
+
*
|
|
602
|
+
* Updates ride a systemd timer and NEVER runtime self-replacement. The agent
|
|
603
|
+
* removed runtime self-update after the 2026-07-19 eviction outage; a daemon
|
|
604
|
+
* that swaps its own binary mid-run has exactly the same failure shape. Here
|
|
605
|
+
* the package is replaced by a separate oneshot unit, and the daemon is
|
|
606
|
+
* restarted afterwards by systemd.
|
|
607
|
+
*
|
|
608
|
+
* Three independent pieces, each with its own decision:
|
|
609
|
+
*
|
|
610
|
+
* - **The package.** Installed at the EXACT version Conveyor's desired state
|
|
611
|
+
* names, never `@latest` — the npm dist-tag is a root-execution channel on
|
|
612
|
+
* every enrolled machine, so a compromised publish would reach them all
|
|
613
|
+
* within a day. The daemon restarts only if the version actually moved.
|
|
614
|
+
* - **cloudflared.** `cloudflared update` exits 11 to mean "updated"; that is
|
|
615
|
+
* the only case that needs a tunnel restart.
|
|
616
|
+
* - **k3s.** Only when the owner allowed it AND nothing of Conveyor's is
|
|
617
|
+
* running. A k3s upgrade at the wrong moment interrupts someone's build, and
|
|
618
|
+
* no upgrade is worth that.
|
|
619
|
+
*/
|
|
620
|
+
|
|
621
|
+
interface UpdatePorts {
|
|
622
|
+
exec: Executor;
|
|
623
|
+
log: (line: string) => void;
|
|
624
|
+
/**
|
|
625
|
+
* Absolute paths to the binaries this updater invokes.
|
|
626
|
+
*
|
|
627
|
+
* Absolute, not bare names, because this runs from a systemd oneshot whose
|
|
628
|
+
* default PATH is `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`
|
|
629
|
+
* — which does not include `/root/.bun/bin`, where the installer puts both
|
|
630
|
+
* Bun and this CLI. Bare names made every timed update exit 1 on ENOENT
|
|
631
|
+
* before it did anything, silently, forever.
|
|
632
|
+
*/
|
|
633
|
+
bins: UpdateBinaries;
|
|
634
|
+
}
|
|
635
|
+
interface UpdateBinaries {
|
|
636
|
+
bun: string;
|
|
637
|
+
cli: string;
|
|
638
|
+
cloudflared: string;
|
|
639
|
+
}
|
|
640
|
+
interface UpdateDecisionInput {
|
|
641
|
+
autoUpgradeK3s: boolean;
|
|
642
|
+
/** Null when the count could not be read — treated exactly like "busy". */
|
|
643
|
+
runningConveyorPods: number | null;
|
|
644
|
+
installedK3s: [number, number] | null;
|
|
645
|
+
channelLatestK3s: [number, number] | null;
|
|
646
|
+
}
|
|
647
|
+
type K3sUpgradeDecision = {
|
|
648
|
+
upgrade: true;
|
|
649
|
+
} | {
|
|
650
|
+
upgrade: false;
|
|
651
|
+
reason: string;
|
|
652
|
+
};
|
|
653
|
+
/**
|
|
654
|
+
* Whether to take a k3s upgrade right now.
|
|
655
|
+
*
|
|
656
|
+
* Pure so the table is testable: the interesting cases are "owner said no",
|
|
657
|
+
* "machine is busy", and "already current", and each must produce a distinct
|
|
658
|
+
* reason the owner can read in their health panel.
|
|
659
|
+
*/
|
|
660
|
+
declare function decideK3sUpgrade(input: UpdateDecisionInput): K3sUpgradeDecision;
|
|
661
|
+
interface UpdateResult {
|
|
662
|
+
packageUpdated: boolean;
|
|
663
|
+
cloudflaredUpdated: boolean;
|
|
664
|
+
k3sUpgraded: boolean;
|
|
665
|
+
notes: string[];
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* @param targetVersion The `cli.latestVersion` from desired state. Null or
|
|
669
|
+
* empty skips the package update instead of falling back to a dist-tag.
|
|
670
|
+
*/
|
|
671
|
+
declare function runUpdate(ports: UpdatePorts, decision: K3sUpgradeDecision, channel: string, targetVersion: string | null): Promise<UpdateResult>;
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* `conveyor-k3 setup` — bring a machine from "nothing" to "running builds".
|
|
675
|
+
*
|
|
676
|
+
* Idempotent from end to end. Running it twice is the normal way to repair a
|
|
677
|
+
* half-finished install, so every step checks before it acts: k3s is installed
|
|
678
|
+
* only when absent, cloudflared only when absent, and the units are rewritten
|
|
679
|
+
* only when their content changed.
|
|
680
|
+
*
|
|
681
|
+
* The step order matters in one place. The ServiceAccount token is minted and
|
|
682
|
+
* handed to Conveyor LAST, because that write is what flips the cluster to
|
|
683
|
+
* enabled — so it must not happen until the tunnel, the RBAC, and the units
|
|
684
|
+
* are all actually in place. A cluster marked usable before it is usable sends
|
|
685
|
+
* the owner's next card to a machine that cannot run it.
|
|
686
|
+
*/
|
|
687
|
+
|
|
688
|
+
/** Parse `k3s version v1.31.0+k3s1` into [major, minor]. */
|
|
689
|
+
declare function parseK3sVersion(output: string): [number, number] | null;
|
|
690
|
+
declare function meetsK3sMinimum(version: [number, number] | null): boolean;
|
|
691
|
+
|
|
692
|
+
interface NetworkEndpoint {
|
|
693
|
+
host: string;
|
|
694
|
+
ports: number[];
|
|
695
|
+
}
|
|
696
|
+
interface ResolvedNetworkHost {
|
|
697
|
+
/** Canonical hostname written into the guest's static DNS data. */
|
|
698
|
+
name: string;
|
|
699
|
+
/** An address on WORKER_SUBNET; it is never a real remote address. */
|
|
700
|
+
guestIp: string;
|
|
701
|
+
/** Host-resolved, pinned, globally-routable IPv4 address. */
|
|
702
|
+
address: string;
|
|
703
|
+
ports: number[];
|
|
704
|
+
}
|
|
705
|
+
interface GuestSrvRecord {
|
|
706
|
+
name: string;
|
|
707
|
+
target: string;
|
|
708
|
+
port: number;
|
|
709
|
+
priority: number;
|
|
710
|
+
weight: number;
|
|
711
|
+
}
|
|
712
|
+
interface ResolvedNetwork {
|
|
713
|
+
hosts: ResolvedNetworkHost[];
|
|
714
|
+
/** Static SRV answers needed by cloudflared's documented edge discovery. */
|
|
715
|
+
srv: GuestSrvRecord[];
|
|
716
|
+
}
|
|
717
|
+
/** Alias used by the VM host manager. */
|
|
718
|
+
type NetworkPolicy = ResolvedNetwork;
|
|
719
|
+
type IPv4Lookup = (host: string) => Promise<string[]>;
|
|
720
|
+
type SRVLookup = (name: string) => Promise<Array<{
|
|
721
|
+
name: string;
|
|
722
|
+
port: number;
|
|
723
|
+
priority: number;
|
|
724
|
+
weight: number;
|
|
725
|
+
}>>;
|
|
726
|
+
/** Reject addresses which cannot be a public Internet destination. */
|
|
727
|
+
declare function isGloballyRoutableIPv4(address: string): boolean;
|
|
728
|
+
/**
|
|
729
|
+
* Resolve approved egress destinations on the host before VM creation.
|
|
730
|
+
*
|
|
731
|
+
* The guest receives only synthetic addresses. Its root user may change its
|
|
732
|
+
* resolver or hosts file, but it cannot add a QEMU guestfwd rule, so this does
|
|
733
|
+
* not turn into arbitrary egress. Every answer is checked, rather than picking
|
|
734
|
+
* a public answer from a mixed public/private DNS response.
|
|
735
|
+
*/
|
|
736
|
+
declare function resolveNetwork(endpoints: NetworkEndpoint[], lookup?: IPv4Lookup): Promise<ResolvedNetwork>;
|
|
737
|
+
/**
|
|
738
|
+
* Build static SRV results only when their region targets were explicitly
|
|
739
|
+
* approved. Callers opt into us-region targets by including them in endpoints.
|
|
740
|
+
*/
|
|
741
|
+
declare function cloudflaredSrvRecords(hosts: ResolvedNetworkHost[]): GuestSrvRecord[];
|
|
742
|
+
/**
|
|
743
|
+
* Resolve Cloudflare's current SRV discovery records on the host and retain
|
|
744
|
+
* only targets explicitly present in the fixed endpoint allowlist. This is an
|
|
745
|
+
* opt-in refresh helper: callers must recreate the worker when an answer
|
|
746
|
+
* changes, and a guest never performs this lookup itself.
|
|
747
|
+
*/
|
|
748
|
+
declare function resolveCloudflaredSrvRecords(hosts: ResolvedNetworkHost[], lookup?: SRVLookup): Promise<GuestSrvRecord[]>;
|
|
749
|
+
|
|
750
|
+
interface GuestDnsProvision {
|
|
751
|
+
/** Write verbatim to /etc/conveyor-vm/dns.json (mode 0600). */
|
|
752
|
+
data: string;
|
|
753
|
+
/** Write verbatim to /usr/local/lib/conveyor-vm-static-dns.py (mode 0755). */
|
|
754
|
+
daemon: string;
|
|
755
|
+
/** Write verbatim to /etc/systemd/system/conveyor-vm-static-dns.service. */
|
|
756
|
+
unit: string;
|
|
757
|
+
/** Write verbatim to /etc/resolv.conf after disabling any local stub resolver. */
|
|
758
|
+
resolvConf: string;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* The daemon is deliberately a small authoritative server, not a forwarding
|
|
762
|
+
* resolver. It answers only the creation-time allowlist and returns NXDOMAIN
|
|
763
|
+
* for everything else. The network data is JSON, never interpolated as Python.
|
|
764
|
+
*/
|
|
765
|
+
declare function renderGuestDnsProvision(network: ResolvedNetwork): GuestDnsProvision;
|
|
766
|
+
|
|
767
|
+
interface QemuNetworkPolicy extends ResolvedNetwork {
|
|
768
|
+
sshPort: number;
|
|
769
|
+
}
|
|
770
|
+
/** Host-only information used to tie QEMU file endpoints to one Lima instance. */
|
|
771
|
+
interface QemuPolicyContext {
|
|
772
|
+
instanceDir?: string;
|
|
773
|
+
}
|
|
774
|
+
/** Rewrites only a complete, known Lima QEMU profile; every unknown input fails closed. */
|
|
775
|
+
declare function rewriteQemuArgs(args: string[], network: QemuNetworkPolicy, ncPath?: string, context?: QemuPolicyContext): string[];
|
|
776
|
+
|
|
777
|
+
export { ClusterConnection, type ClusterConnectionConfig, ClusterDaemon, CommandError, type CrictlImage, type DaemonPorts, type Executor, type FileSystemPort$1 as FileSystemPort, type GuestDnsProvision, type GuestSrvRecord, HELP, type ImageInventory, K3S_DISABLED_COMPONENTS, K3S_INSTALL_EXEC, type K3sUpgradeDecision, type MachineState, type NetworkEndpoint, type NetworkPolicy, type QemuNetworkPolicy, type ReconcileContext, type ReconcileResult, type RegistriesFile, type ResolvedNetwork, type ResolvedNetworkHost, type SRVLookup, type UninstallResult, type UnitRenderOptions, VERBS, type Verb, cloudflaredSrvRecords, collectHeartbeat, configuredAuthHosts, countRunningConveyorPods, createMemoryFileSystem, decideK3sUpgrade, isGloballyRoutableIPv4, isVerb, meetsK3sMinimum, missingAuthHosts, nodeFileSystem, ownerConfiguredKeys, ownerConfiguredKubeletFlags, parseArgs, parseK3sVersion, parseRegistries, pruneStrandedImages, readImageInventory, readState, reconcile, registryAdd, registryList, renderAllUnits, renderGuestDnsProvision, renderK3sDropIn, renderKubeletDropIn, renderRegistryAuthSummary, resolveCloudflaredSrvRecords, resolveNetwork, rewriteQemuArgs, runOrThrow, runUpdate, selectStrandedImages, serializeRegistries, spawnExecutor, uninstall, upsertRegistryAuth, warmImages, writeState };
|