Custom hypervisor adapter
Every backend Aetheris supports is a class implementing the
HypervisorDriver interface from src/lib/adapters/hypervisors/types.ts in
aetheris-app. Adding a new backend is a single-file change plus one registry
entry.
The contract
interface HypervisorDriver {
readonly kind: HypervisorKind; // "pterodactyl" | "proxmox" | "virtfusion" | <yours>
readonly configName: string;
health(): Promise<HealthReport>;
listNodes(): Promise<NodeDefinition[]>;
getNode(nodeExternalId: string): Promise<NodeDefinition>;
listAllocations(nodeExternalId: string): Promise<Allocation[]>;
listEggs(nestExternalId?: string): Promise<EggDefinition[]>;
listServers(filter?: ListServersFilter): Promise<ServerInfo[]>;
getServer(serverExternalId: string): Promise<ServerInfo>;
provision(request: ProvisionRequest): Promise<ProvisionResult>;
rebuild(request: RebuildRequest): Promise<ServerInfo>;
suspend(serverExternalId: string, reason?: string): Promise<ServerInfo>;
unsuspend(serverExternalId: string): Promise<ServerInfo>;
terminate(serverExternalId: string, options?: TerminateOptions): Promise<void>;
power(serverExternalId: string, signal: PowerSignal): Promise<void>;
getTelemetry(serverExternalId: string): Promise<TelemetrySample>;
openConsole(serverExternalId: string): Promise<ConsoleSession>;
listBackups(serverExternalId: string): Promise<BackupInfo[]>;
createBackup(serverExternalId: string, name: string): Promise<BackupInfo>;
restoreBackup(serverExternalId: string, backupExternalId: string): Promise<void>;
deleteBackup(serverExternalId: string, backupExternalId: string): Promise<void>;
supports(type: VirtualizationType): boolean;
}Implementation rules
- Statelessness. Drivers must not hold per-request state. Configuration is fixed at construction; everything else belongs to the caller or the backend. This lets one instance serve the API layer and workers.
- Errors. Throw
HypervisorDriverErrorwith akind, aDriverErrorCodefrom the union (UNAUTHORIZED,NOT_FOUND,RATE_LIMITED,VALIDATION,TIMEOUT,CONFLICT,BACKEND_ERROR,NETWORK,NOT_SUPPORTED) and astatuswhen the backend returns one. - Rate limiting. Backends with rate limits must throttle outbound
requests. Copy the token-bucket class from
pterodactyl.ts. - Timeouts. Every fetch must carry a timeout
(
AbortSignal.timeout); the config fieldtimeoutMsis the knob. - Type guards. Validate backend responses before use. Malformed payloads
must raise
HypervisorDriverError, never surface as undefined access. - Console. If the backend has no console API, throw
NOT_SUPPORTED; the portal falls back to a deep link.
Registration
- Add the kind to the
HypervisorKindunion intypes.tsand a config interface toHypervisorConfig. - Implement the driver in
src/lib/adapters/hypervisors/<name>.ts. - Add a zod schema to
src/lib/adapters/hypervisors/index.tsand extend theinstantiateswitch. - Add a row to
DRIVER_CATALOGdeclaring the supported virtualization types. - Add the enum value to the Prisma
HypervisorKindenum and migrate.
Example skeleton
import {
HypervisorDriverError,
type HypervisorDriver,
type ProvisionRequest,
type ProvisionResult
} from "./types";
export class ExampleDriver implements HypervisorDriver {
readonly kind = "example" as const;
readonly configName: string;
constructor(config: { name: string; baseUrl: string; token: string }) {
this.configName = config.name;
// store config, build the token-bucket limiter
}
supports(type: "vm" | "container"): boolean {
return type === "vm";
}
async provision(request: ProvisionRequest): Promise<ProvisionResult> {
const response = await fetch(`${this.baseUrl}/vms`, {
method: "POST",
headers: { Authorization: `Bearer ${this.token}` },
body: JSON.stringify({ name: request.name, cores: request.resources.vcpu }),
signal: AbortSignal.timeout(15_000)
});
if (!response.ok) {
throw new HypervisorDriverError({
kind: this.kind,
code: "BACKEND_ERROR",
message: `provision failed with HTTP ${response.status}`,
status: response.status
});
}
const created = (await response.json()) as { id: string };
return { serverExternalId: created.id, state: "installing" };
}
// ... all remaining interface methods
}Testing
Add a driver test under src/lib/adapters/hypervisors/__tests__ that mocks
fetch and asserts: success paths, error mapping per status code, rate
limiting, and timeout behavior. Run with npm test.