@rallycry/conveyor-k3 0.1.10 → 0.1.12

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/index.d.ts CHANGED
@@ -1,10 +1,13 @@
1
- import { PersonalClusterEnrollmentBundle, PersonalClusterEnrollmentCompletion, PersonalClusterDesiredState, PersonalClusterHeartbeat, PersonalClusterKubeletState } from '@project/shared';
1
+ import { PersonalClusterEnrollmentBundle, PersonalClusterEnrollmentCompletion, PersonalClusterDesiredState, PersonalClusterHeartbeat, PersonalClusterWorkloads, PersonalClusterKubeletState } from '@project/shared';
2
2
 
3
3
  interface ClusterConnectionConfig {
4
4
  apiUrl: string;
5
5
  clusterToken: string;
6
6
  /** Test-only override; production always reads the root-owned guest key. */
7
7
  deviceKeyPath?: string;
8
+ /** Test-only overrides; production reads the host-written control aliases. */
9
+ readControlFile?: (path: string) => string | null;
10
+ controlFileExists?: (path: string) => boolean;
8
11
  }
9
12
  declare class ClusterConnection {
10
13
  private socket;
@@ -19,8 +22,8 @@ declare class ClusterConnection {
19
22
  enroll(): Promise<PersonalClusterEnrollmentBundle>;
20
23
  completeEnrollment(payload: PersonalClusterEnrollmentCompletion): Promise<void>;
21
24
  requestDesiredState(): Promise<PersonalClusterDesiredState>;
22
- /** Fire-and-forget: a lost heartbeat is covered by the next one. */
23
- sendHeartbeat(payload: PersonalClusterHeartbeat): void;
25
+ /** Old servers still receive liveness even when they do not acknowledge inspection data. */
26
+ sendHeartbeat(payload: PersonalClusterHeartbeat): Promise<PersonalClusterWorkloads | undefined>;
24
27
  /**
25
28
  * Tell Conveyor this machine is stopping, so placement skips it at once
26
29
  * instead of waiting out the stale window.
@@ -68,7 +71,8 @@ interface FileSystemPort$1 {
68
71
  readFile(path: string): Promise<string | null>;
69
72
  writeFile(path: string, content: string, mode?: number): Promise<void>;
70
73
  removeFile(path: string): Promise<void>;
71
- ensureDir(path: string): Promise<void>;
74
+ /** With a mode, an existing directory is set to it as well. */
75
+ ensureDir(path: string, mode?: number): Promise<void>;
72
76
  }
73
77
  declare const nodeFileSystem: FileSystemPort$1;
74
78
  /** In-memory filesystem for tests. Exported so suites share one shape. */
@@ -78,12 +82,32 @@ declare function createMemoryFileSystem(seed?: Record<string, string>): FileSyst
78
82
  removed: string[];
79
83
  };
80
84
 
85
+ /**
86
+ * The long-lived daemon: connect, converge, report, repeat.
87
+ *
88
+ * Three clocks, deliberately independent:
89
+ *
90
+ * - **Heartbeat, 30s.** Placement reads what this writes, so it is the one
91
+ * thing that must never stall behind slower work.
92
+ * - **Warm, hourly.** Re-pull the wanted refs so a bake never costs the next
93
+ * card a cold multi-gigabyte pull.
94
+ * - **Reconcile + prune, daily** — plus on connect and whenever Conveyor says
95
+ * the desired state changed.
96
+ *
97
+ * The daily reconcile is what makes this a reconciler rather than a command
98
+ * listener: a machine that was powered off, or whose sync nudge landed on
99
+ * another API replica, still converges on its own.
100
+ */
101
+
81
102
  interface DaemonPorts {
82
103
  exec: Executor;
83
104
  fs: FileSystemPort$1;
84
105
  connection: ClusterConnection;
85
106
  cliVersion: string;
107
+ writeWorkloads?: (data: PersonalClusterWorkloads | undefined) => Promise<void>;
86
108
  log: (line: string) => void;
109
+ /** The in-cluster registry's HTTP API; the global `fetch` when absent. */
110
+ fetch?: (url: string, init?: RequestInit) => Promise<Response>;
87
111
  }
88
112
  declare class ClusterDaemon {
89
113
  private readonly ports;
@@ -104,7 +128,13 @@ declare class ClusterDaemon {
104
128
  * the next reconcile silently clear them.
105
129
  */
106
130
  private warmWarnings;
131
+ /** Content hash labels by image id, so each image is inspected once. */
132
+ private readonly localImageHashes;
133
+ /** How long the in-cluster registry has been not ready, across beats. */
134
+ private readonly registryWatch;
107
135
  private timers;
136
+ /** When the last store walk started, so one that failed is not retried on every reconcile. */
137
+ private storeWalkStartedAt;
108
138
  /** Serializes reconcile against itself — a sync nudge during a reconcile
109
139
  * must queue, not interleave two kubectl applies of the same manifests. */
110
140
  private reconciling;
@@ -123,6 +153,14 @@ declare class ClusterDaemon {
123
153
  * generations in between.
124
154
  */
125
155
  private warmAndPruneIfDue;
156
+ /**
157
+ * Measure k3s's containerd store when the last measurement is old enough,
158
+ * and keep the result for the heartbeat. After a prune, so the number it
159
+ * reports is what the prune left. A failed measurement keeps the last one.
160
+ */
161
+ private measureStoreIfDue;
162
+ /** The store size the heartbeat reports: the last measurement, null before one. */
163
+ private get measuredStoreBytes();
126
164
  /** Pre-pull the wanted refs. Silent on success; that is the normal case. */
127
165
  warm(): Promise<void>;
128
166
  /**
@@ -150,66 +188,15 @@ declare class ClusterDaemon {
150
188
  private strandedBytesExceedLimit;
151
189
  prune(): Promise<void>;
152
190
  private buildHeartbeat;
191
+ private persistWorkloads;
192
+ private beating?;
193
+ private stopped;
153
194
  beat(): Promise<void>;
195
+ private runBeat;
154
196
  }
155
197
 
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>;
198
+ /** The `fetch` the registry API calls go through, injectable for tests. */
199
+ type RegistryFetch = (url: string, init?: RequestInit) => Promise<Response>;
213
200
 
214
201
  /** One entry of `crictl images -o json`. */
215
202
  interface CrictlImage {
@@ -272,6 +259,22 @@ interface WarmResult {
272
259
  */
273
260
  declare function warmImages(exec: Executor, wantedImages: string[], unauthenticatedHosts: string[]): Promise<WarmResult>;
274
261
 
262
+ /**
263
+ * Which locally baked images this machine holds, for placement.
264
+ *
265
+ * A Bake Locally project's image exists only where a bake imported it: the
266
+ * runner's own machine, or the managed VM on it. When one account has several
267
+ * instances, placement has to send a card to an instance that holds the image,
268
+ * and should prefer one holding the latest bake. So the heartbeat lists every
269
+ * `localhost/` ref, with the content hash the bake stamped on the image.
270
+ *
271
+ * The hash takes one `crictl inspecti` per image, cached by image id: an id
272
+ * names exactly one image config, so its label never changes.
273
+ */
274
+
275
+ /** Image id to the content hash label on that image, null when it has none. */
276
+ type LocalImageHashCache = Map<string, string | null>;
277
+
275
278
  interface TelemetryState {
276
279
  cliVersion: string;
277
280
  appliedDesiredStateVersion: number;
@@ -286,10 +289,32 @@ interface TelemetryState {
286
289
  * no reconcile has read one yet. Drives the GC-pressure warning below.
287
290
  */
288
291
  imageGcHighThreshold?: number | null;
292
+ /** The daemon's own cache of content hash labels, kept across beats. */
293
+ localImageHashes?: LocalImageHashCache;
294
+ /** Present while desired state turns the in-cluster registry on. */
295
+ localRegistry?: LocalRegistryWatch;
296
+ /**
297
+ * The last measured size of k3s's containerd store. Null (or absent) until
298
+ * the first measurement, and the heartbeat says null then: a 0 would read
299
+ * as an empty store.
300
+ */
301
+ k3sStoreBytes?: number | null;
289
302
  }
290
303
  interface TelemetryPorts {
291
304
  exec: Executor;
292
305
  readFile(path: string): Promise<string | null>;
306
+ /** Injectable for tests; the registry probe uses the global otherwise. */
307
+ fetch?: RegistryFetch;
308
+ }
309
+ /** The daemon's memory of the registry's readiness, kept across beats. */
310
+ interface LocalRegistryWatch {
311
+ /** When a beat first found the registry not ready; null while it is. */
312
+ notReadySince: number | null;
313
+ /**
314
+ * Until when a cleanup stops the registry on purpose. The clock does not
315
+ * run until then, so the panel never calls that stop an outage.
316
+ */
317
+ pausedUntil?: number | null;
293
318
  }
294
319
  /**
295
320
  * Conveyor pods running here, across every namespace it manages — or null when
@@ -308,6 +333,71 @@ interface TelemetryPorts {
308
333
  declare function countRunningConveyorPods(exec: Executor): Promise<number | null>;
309
334
  declare function collectHeartbeat(ports: TelemetryPorts, state: TelemetryState, inventory: ImageInventory | null): Promise<PersonalClusterHeartbeat>;
310
335
 
336
+ /**
337
+ * Converge the machine onto Conveyor's desired state.
338
+ *
339
+ * This is what "manages itself" means in code. Conveyor publishes what the
340
+ * cluster SHOULD look like; this function makes it so, and it runs on connect,
341
+ * on request, and daily. Nothing here is a command from the server — a machine
342
+ * that was powered off for a week catches up on its next run, and a practice
343
+ * Conveyor changes reaches every enrolled box without anyone touching one.
344
+ *
345
+ * Three properties every step keeps:
346
+ *
347
+ * 1. **Idempotent.** Running twice changes nothing the first run already did.
348
+ * 2. **Independently fallible.** A step that fails records a warning and the
349
+ * rest still run. A cluster with one bad namespace is not a reason to skip
350
+ * image hygiene.
351
+ * 3. **Additive.** Nothing here deletes a namespace, a workload, or an image
352
+ * that is in use. The machine belongs to its owner. The one delete is an
353
+ * EMPTY pool namespace of a project the machine no longer serves, under
354
+ * local bounds the server cannot widen — see `./prune-namespaces.js`.
355
+ * 4. **Bounded.** The server does not get to say what runs here. The one field
356
+ * it sends as raw YAML is checked against a local kind allowlist before it
357
+ * reaches `kubectl` — see `../lib/manifest-guard.js`.
358
+ */
359
+
360
+ /** The filesystem operations reconcile needs, injectable for tests. */
361
+ interface FileSystemPort {
362
+ readFile(path: string): Promise<string | null>;
363
+ writeFile(path: string, content: string, mode?: number): Promise<void>;
364
+ removeFile(path: string): Promise<void>;
365
+ ensureDir(path: string, mode?: number): Promise<void>;
366
+ }
367
+ interface ReconcileContext {
368
+ exec: Executor;
369
+ fs: FileSystemPort;
370
+ /**
371
+ * Conveyor pods currently running here, or null when that could not be
372
+ * determined. A disruptive step is unsafe above zero AND unsafe on null —
373
+ * "I could not ask" is not "nothing is running".
374
+ */
375
+ runningConveyorPods: number | null;
376
+ }
377
+ interface ReconcileResult {
378
+ appliedVersion: number;
379
+ warnings: string[];
380
+ /**
381
+ * True when this pass restarted k3s: for new kubelet settings, or to load
382
+ * the local image registry's mirror. At most one restart per pass.
383
+ */
384
+ restartedK3s: boolean;
385
+ /** Kubelet flags left to the owner because their own config sets them. */
386
+ skippedKubeletFlags: string[];
387
+ /**
388
+ * The image-GC high threshold the kubelet is actually enforcing — ours, or
389
+ * the owner's when their own config.yaml set it and the drop-in stood down.
390
+ * Null when neither side named one. Reported so the heartbeat can warn about
391
+ * GC pressure against the number in force rather than the one we sent.
392
+ */
393
+ effectiveImageGcHighThreshold: number | null;
394
+ /** True when a wrong-valued PriorityClass was deleted AND put back. */
395
+ recreatedPriorityClass: boolean;
396
+ /** Empty pool namespaces of projects this machine no longer serves, removed. */
397
+ prunedNamespaces: string[];
398
+ }
399
+ declare function reconcile(ctx: ReconcileContext, desired: PersonalClusterDesiredState): Promise<ReconcileResult>;
400
+
311
401
  interface RegistryAuth {
312
402
  username?: string;
313
403
  password?: string;
@@ -486,6 +576,15 @@ interface MachineState {
486
576
  * wanted" is not a safe rule to unpin on. Only what we pinned, we unpin.
487
577
  */
488
578
  pinnedImages?: string[];
579
+ /** When the in-cluster registry's garbage was last collected. */
580
+ lastRegistryGcAt?: string | null;
581
+ /**
582
+ * The size of k3s's containerd store at the last measurement, and when that
583
+ * was. Kept so the heartbeat reports it between the hourly measurements,
584
+ * and a daemon restart does not walk the store again straight away.
585
+ */
586
+ k3sStoreBytes?: number;
587
+ k3sStoreMeasuredAt?: string;
489
588
  }
490
589
  declare function readState(fs: FileSystemPort$1): Promise<MachineState>;
491
590
  declare function writeState(fs: FileSystemPort$1, state: MachineState): Promise<void>;
@@ -514,15 +613,17 @@ interface ParsedArgs {
514
613
  * this surface is nine verbs and six flags, not a general CLI framework.
515
614
  */
516
615
  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";
616
+ 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 registry gc Delete superseded shadow-bake images from the image\n registry inside this cluster. Runs daily on its own,\n and only while no Conveyor pod or bake is running.\n --dry-run list what it would delete\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, state, and image\n registry. Your k3s config, your own registries.yaml\n entries, your workloads, and the registry's data are\n left alone.\n --purge-k3s --yes also remove k3s itself and the\n registry's data\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
617
 
519
618
  /**
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
619
+ * `conveyor-k3 registry add|list|gc` — where an owner adds registry
620
+ * credentials, and cleans up the registry inside the cluster.
621
+ *
622
+ * `add` is the one command that writes a credential into `registries.yaml`.
623
+ * The reconcile loop writes that file for one reason only: the mirror for the
624
+ * in-cluster registry's host (`lib/registries.ts` lists both writers). Both
625
+ * take a timestamped backup first, and both merge rather than template — so a
626
+ * machine pulling through its own LAN mirror keeps that mirror, its TLS
526
627
  * settings, and every other host it had configured.
527
628
  *
528
629
  * A restart is required and is not optional: k3s reads `registries.yaml` at
@@ -563,23 +664,12 @@ declare function registryList(ports: RegistryPorts, requiredHosts: string[]): Pr
563
664
  */
564
665
  declare function renderRegistryAuthSummary(missing: string[]): string[];
565
666
 
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
667
  interface UninstallPorts {
580
668
  exec: Executor;
581
669
  fs: FileSystemPort$1;
582
670
  log: (line: string) => void;
671
+ /** Injected so the `registries.yaml` backup's name is deterministic in tests. */
672
+ now?: () => Date;
583
673
  }
584
674
  interface UninstallOptions {
585
675
  /** Also run k3s-uninstall.sh. Requires `confirmed`. */
@@ -689,6 +779,27 @@ declare function runUpdate(ports: UpdatePorts, decision: K3sUpgradeDecision, cha
689
779
  declare function parseK3sVersion(output: string): [number, number] | null;
690
780
  declare function meetsK3sMinimum(version: [number, number] | null): boolean;
691
781
 
782
+ /**
783
+ * Capacity profiles: how many guest connections a VM's host relays at once, how
784
+ * much one connection may carry, and how fast the boot disk may go.
785
+ *
786
+ * Pure and import-free on purpose: the QEMU wrapper bundle (dist/vm-qemu.js)
787
+ * includes it, and that bundle starts once for every guest connection.
788
+ */
789
+ /** `custom` is any set of values that no longer matches a preset. */
790
+ type CapacityProfile = "default" | "server" | "custom";
791
+ /** What the host enforces, in bytes, rendered into network.json as `limits`. */
792
+ interface CapacityLimits {
793
+ schema: 1;
794
+ profile: CapacityProfile;
795
+ relaySlots: number;
796
+ /** Slots that only control-plane relays (the tunnel and the daemon) may take. */
797
+ reservedRelaySlots: number;
798
+ relayMaxBytes: number;
799
+ diskBytesPerSecond: number;
800
+ diskIops: number;
801
+ }
802
+
692
803
  interface NetworkEndpoint {
693
804
  host: string;
694
805
  ports: number[];
@@ -701,6 +812,8 @@ interface ResolvedNetworkHost {
701
812
  /** Host-resolved, pinned, globally-routable IPv4 address. */
702
813
  address: string;
703
814
  ports: number[];
815
+ /** A control-plane destination, whose relays may use the reserved slots. */
816
+ reserve?: "control";
704
817
  }
705
818
  interface GuestSrvRecord {
706
819
  name: string;
@@ -710,9 +823,17 @@ interface GuestSrvRecord {
710
823
  weight: number;
711
824
  }
712
825
  interface ResolvedNetwork {
826
+ publicWeb?: boolean;
713
827
  hosts: ResolvedNetworkHost[];
714
828
  /** Static SRV answers needed by cloudflared's documented edge discovery. */
715
829
  srv: GuestSrvRecord[];
830
+ /**
831
+ * Daemon-only aliases of the Conveyor API. Guest DNS never serves them, so
832
+ * only the daemon, which dials them by address, can use the reserved slots.
833
+ */
834
+ control?: ResolvedNetworkHost[];
835
+ /** What the host enforces. Absent in a document an older CLI wrote. */
836
+ limits?: CapacityLimits;
716
837
  }
717
838
  /** Alias used by the VM host manager. */
718
839
  type NetworkPolicy = ResolvedNetwork;
@@ -770,11 +891,72 @@ interface QemuNetworkPolicy extends ResolvedNetwork {
770
891
  /** Host-only information used to tie QEMU file endpoints to one Lima instance. */
771
892
  interface QemuPolicyContext {
772
893
  instanceDir?: string;
894
+ /** The Lima instance name, which Lima passes as `-name lima-<name>`. */
895
+ vmName?: string;
896
+ /** The boot-disk throttle, from the network policy's capacity limits. */
897
+ disk?: {
898
+ bps: number;
899
+ iops: number;
900
+ };
773
901
  }
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[];
902
+ /**
903
+ * Rewrites only a complete, known Lima QEMU profile; every unknown input fails
904
+ * closed. The network's capacity limits set the boot-disk throttle.
905
+ */
906
+ declare function rewriteQemuArgs(args: string[], network: QemuNetworkPolicy, ncPath?: string, hostContext?: QemuPolicyContext): string[];
776
907
 
908
+ /** The largest whole-GiB guest whose host cost (see vmHostMemoryGiB) fits in the budget. */
909
+ declare function maximumGuestMemoryGiBWithin(budgetGiB: number, relaySlots?: number): number;
777
910
  /** Reserve 25% of physical host RAM after accounting for VM overhead. */
778
- declare function maximumGuestMemoryGiB(hostBytes: number): number;
911
+ declare function maximumGuestMemoryGiB(hostBytes: number, relaySlots?: number): number;
912
+
913
+ /** `--instance` omitted or `default`: the original VM, whose paths never move. */
914
+ declare const DEFAULT_INSTANCE = "default";
915
+ /** The `--instance` flag's value. Omitted or `default` selects the default VM. */
916
+ declare function parseInstanceName(value: unknown): string;
917
+
918
+ /** One VM's host paths. The default VM keeps its original root. */
919
+ declare function hostPaths(instance?: string): {
920
+ root: string;
921
+ limaHome: string;
922
+ state: string;
923
+ network: string;
924
+ template: string;
925
+ wrapper: string;
926
+ wrapperJs: string;
927
+ applied: string;
928
+ guard: string;
929
+ startRequest: string;
930
+ policyTransaction: string;
931
+ lock: string;
932
+ /** Written by an owner stop, so a supervising service does not restart the VM. */
933
+ stopIntent: string;
934
+ instance: string;
935
+ vmName: string;
936
+ instanceDir: string;
937
+ limaConfig: string;
938
+ };
939
+ /** Every host path of one VM under `root`. Pure, so tests inject a temporary root. */
940
+ declare function pathsForRoot(root: string, instance?: string): {
941
+ root: string;
942
+ limaHome: string;
943
+ state: string;
944
+ network: string;
945
+ template: string;
946
+ wrapper: string;
947
+ wrapperJs: string;
948
+ applied: string;
949
+ guard: string;
950
+ startRequest: string;
951
+ policyTransaction: string;
952
+ lock: string;
953
+ /** Written by an owner stop, so a supervising service does not restart the VM. */
954
+ stopIntent: string;
955
+ instance: string;
956
+ vmName: string;
957
+ instanceDir: string;
958
+ limaConfig: string;
959
+ };
960
+ type HostPaths = ReturnType<typeof pathsForRoot>;
779
961
 
780
- 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, maximumGuestMemoryGiB, 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 };
962
+ export { ClusterConnection, type ClusterConnectionConfig, ClusterDaemon, CommandError, type CrictlImage, DEFAULT_INSTANCE, type DaemonPorts, type Executor, type FileSystemPort$1 as FileSystemPort, type GuestDnsProvision, type GuestSrvRecord, HELP, type HostPaths, 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, hostPaths, isGloballyRoutableIPv4, isVerb, maximumGuestMemoryGiB, maximumGuestMemoryGiBWithin, meetsK3sMinimum, missingAuthHosts, nodeFileSystem, ownerConfiguredKeys, ownerConfiguredKubeletFlags, parseArgs, parseInstanceName, 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 };