/** * Guest lifecycle on a machine — ours or a tenant's, identically. * * ## Why each guest is its own systemd unit * * The obvious shape is for the agent to launch qemu and keep track of it. That * makes every guest a CHILD of the agent, and it means upgrading the agent — * `systemctl restart fz-agent` — kills every tenant compute on the box. A host * you cannot patch without an outage is a host that does not get patched. * * So the agent never owns a running guest. It writes a unit, asks systemd to * start it, and walks away. systemd owns the process, restarts it after a host * reboot, and survives the agent being replaced underneath it. * * ## Why the inventory lives on the root host * * The API is authoritative for what SHOULD exist. The host is authoritative for * what DOES. Those disagree during an outage, a partial provision, or an * operator's manual `kill` — and the host's answer is the one that matters when * somebody is about to overwrite a disk. * * Persisting it here also means the host can bring its guests back with no * control plane at all: the units are on disk, systemd starts them, and the * platform reconciles later. A machine that needs the API to boot its tenants' * computes is a machine that takes them down with it. */ export interface GuestSpec { /** Stable name. Becomes the unit name, so it must be a valid identifier. */ name: string; vcpu: number; /** Exclusive host CPUs assigned to this guest's whole QEMU service. */ allowedCpus?: string; /** NUMA memory nodes paired with allowedCpus, when the host exposes them. */ allowedMemoryNodes?: string; memoryGib: number; /** Block device or image path. Raw LVM on our metal; a file is allowed. */ disk: string; /** Host-at-rest LUKS2 envelope. The key is a host-sealed systemd credential. */ diskEncryption?: { source: string; mapper: string; credentialName: string; credentialPath: string; }; /** cloud-init seed, attached read-only. */ seed: string; bridge: string; mac: string; /** Stable tap owned by this unit. Required when egress is shaped. */ tap?: string; /** Sustained guarantee and borrowable ceiling, in Mbit/s. */ egress?: { guaranteedMbps: number; burstMbps: number; }; /** * SEV-SNP, when the host can prove it. * * `cbitpos` is a property of the CPU, not a preference — it is read from the * host at enrolment and carried here, because guessing it produces a guest * that boots without memory encryption while claiming to have it. */ confidential?: { cbitpos: number; reducedPhysBits: number; policy: string; }; consoleLog?: string; } export declare class ComputeError extends Error { readonly code: 'BAD_NAME' | 'BAD_SPEC'; constructor(code: 'BAD_NAME' | 'BAD_SPEC', message: string); } export declare const unitName: (name: string) => string; /** The qemu command, as an argv array. Never a shell string. */ export declare function qemuArgv(spec: GuestSpec): string[]; /** * The unit file. * * `Restart=always` is the point of using systemd at all: a guest that dies at * 3am comes back without anybody being paged, and a host reboot brings every * tenant's compute up in the same state it was in. */ export declare function guestUnit(spec: GuestSpec): string; export interface RunningGuest { name: string; pid: number; disks: string[]; } /** * What is ACTUALLY running, read from process arguments. * * Not from libvirt, and not from the inventory. SEV-SNP guests are launched * with raw qemu because libvirt on most hosts cannot express `sev-snp-guest`, * so `virsh list` reports an empty machine while three confidential guests are * running — and an operator who trusts it will overwrite their disks. That is * not hypothetical; it is how this function came to exist. */ export declare function parseCensus(psOutput: string): RunningGuest[]; /** Whether a device is in use by any running guest. */ export declare const deviceInUse: (census: RunningGuest[], device: string) => boolean; /** * Rate-limit a guest's egress on its tap device. * * Without this every guest can saturate the host's whole uplink — 25 Gb/s on * our metal — so one tenant's runaway job starves the other guests and the * hypervisor with them. It is also what makes bandwidth SELLABLE: a compute * plan cannot state a number the machine does not enforce. * * Shaped on EGRESS only. Ingress arrives whether we want it or not; policing it * at the tap drops packets that already crossed the expensive link, which costs * the same and adds retransmits. Ingress belongs upstream, at the edge. * * The tap receives packets emitted by the guest, so the limiter belongs on the * tap's INGRESS hook. A root qdisc there shapes traffic going into the guest and * looks correct in `tc` while enforcing the opposite direction. * * The guaranteed rate is a placement promise: the host reserves enough uplink * for every admitted guest. The per-tap policer enforces the burst ceiling. */ export declare function shapeEgressCommands(tap: string, mbps: number, burstMbps?: number): string[]; /** * The same contract as `shapeEgressCommands`, expressed without a shell for a * systemd guest unit. Every argument is generated from validated numbers and a * validated device name; no tenant-controlled command text is evaluated. */ export declare function shapeEgressUnitDirectives(tap: string, guaranteedMbps: number, burstMbps: number): string[]; /** The tap a guest's NIC is attached to, from the census. */ export declare const tapFor: (guestIndex: number) => string;