Skip to content

Ghaf Architecture for Developers

This section covers the internal architecture of the Ghaf framework from a developer’s perspective. Understanding these concepts is essential for:

  • Contributing new modules or features to Ghaf
  • Creating new VM types
  • Building downstream projects on top of Ghaf
  • Debugging configuration issues

Ghaf is built on the principle of security through compartmentalization. Instead of running all applications in a single operating system, Ghaf isolates different security domains into separate virtual machines (VMs).

AspectVMsContainers
IsolationHardware-level via hypervisorKernel-level via namespaces
Attack surfaceMinimal (hypervisor only)Larger (shared kernel)
Kernel exploitsContained to single VMCan escape to host
Resource overheadHigherLower

For security-critical applications, the stronger isolation of VMs outweighs the performance cost.

The Ghaf host runs only what’s necessary to orchestrate VMs:

┌─────────────────────────────────────────────────────────────┐
│ GHAF HOST │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ microvm │ │ GIVC │ │ Hardware Drivers │ │
│ │ daemon │ │ (IPC) │ │ (passthrough) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ GUI VM │ │ Net VM │ │ Audio VM │ │ App VMs │
│ (display) │ │ (network) │ │ (sound) │ │ (apps) │
└───────────┘ └───────────┘ └───────────┘ └───────────┘

Each VM has a specific security domain:

  • GUI VM: Display, input devices, desktop environment
  • Net VM: All network interfaces and traffic
  • Audio VM: Sound hardware and processing
  • App VMs: Individual applications (browser, messaging, etc.)

Ghaf uses a layered configuration system that flows from the host to each VM.

┌─────────────────────────────────────────────────────────────┐
│ HOST NIXOS CONFIGURATION │
│ │
│ • Defines which VMs exist │
│ • Sets global configuration (ghaf.global-config) │
│ • Configures hardware passthrough │
│ • Orchestrates VM lifecycle │
└─────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ globalConfig│ │ hostConfig │ │ VM Config │
│ │ │ │ │ │
│ Same for │ │ Different │ │ VM's own │
│ all VMs │ │ per VM │ │ settings │
└─────────────┘ └─────────────┘ └─────────────┘

The top-level configuration defined in targets/ or downstream projects. This is where you:

  • Select which VMs to enable
  • Configure ghaf.global-config with desired profile
  • Set hardware-specific options
  • Define target-specific customizations
targets/laptop/flake-module.nix
{
ghaf.global-config = lib.ghaf.profiles.debug;
ghaf.virtualization.microvm = {
guivm.enable = true;
netvm.enable = true;
audiovm.enable = true;
};
}

Settings that should be identical across all VMs. These propagate automatically via specialArgs.

Use globalConfig for:

  • Debug mode
  • SSH access
  • Logging configuration
  • GIVC settings
  • Feature flags (fingerprint, yubikey, etc.)
# Accessed in VM modules via specialArgs
{ globalConfig, ... }:
{
config = {
ghaf.profiles.debug.enable = globalConfig.debug.enable;
};
}

Settings that are different for each VM but derived from the host. These also propagate via specialArgs.

Use hostConfig for:

  • VM name and type
  • Network configuration (IP, MAC)
  • User configuration
  • Passthrough device assignments
# Accessed in VM modules via specialArgs
{ hostConfig, ... }:
{
config = {
networking.hostName = hostConfig.vmName;
networking.interfaces.ethint0.ipv4.addresses = [{
address = hostConfig.networking.thisVm.ipv4;
prefixLength = 24;
}];
};
}

The following diagram shows how settings flow from host to VM:

┌─────────────────────────────────────────────────────────────────┐
│ TARGET CONFIGURATION (targets/laptop/flake-module.nix) │
│ │
│ ghaf.global-config = lib.ghaf.profiles.debug; │
│ │
│ ghaf.virtualization.microvm.guivm = { │
│ enable = true; │
│ evaluatedConfig = ...extendModules { modules = [...] }; │
│ }; │
└─────────────────────────────────────────────────────────────────┘
│ 1. Profile creates base config
┌─────────────────────────────────────────────────────────────────┐
│ PROFILE (modules/profiles/laptop-x86.nix) │
│ │
│ guivmBase = lib.nixosSystem { │
│ specialArgs = lib.ghaf.vm.mkSpecialArgs { │
│ inherit lib inputs; │
│ globalConfig = config.ghaf.global-config; ◄── From host │
│ hostConfig = lib.ghaf.vm.mkHostConfig { ... }; │
│ }; │
│ modules = [ inputs.self.nixosModules.guivm-base ]; │
│ }; │
└─────────────────────────────────────────────────────────────────┘
│ 2. Base module receives via specialArgs
┌─────────────────────────────────────────────────────────────────┐
│ VM BASE MODULE (modules/microvm/sysvms/guivm-base.nix) │
│ │
│ { config, lib, pkgs, globalConfig, hostConfig, ... }: │
│ { │
│ # Use globalConfig for inherited settings │
│ ghaf.profiles.debug.enable = globalConfig.debug.enable; │
│ │
│ # Use hostConfig for VM-specific settings │
│ networking.hostName = hostConfig.vmName; │
│ } │
└─────────────────────────────────────────────────────────────────┘
│ 3. VM module consumes evaluatedConfig
┌─────────────────────────────────────────────────────────────────┐
│ VM ORCHESTRATION MODULE (modules/microvm/sysvms/guivm.nix) │
│ │
│ microvm.vms."gui-vm" = { │
│ inherit (cfg) evaluatedConfig; ◄── Fully evaluated config │
│ }; │
└─────────────────────────────────────────────────────────────────┘

The specialArgs parameter to evalModules provides values that are available to all modules without needing to be passed explicitly. This avoids the anti-pattern of threading values through module parameters.

# specialArgs makes these available everywhere in the module tree
specialArgs = {
inherit lib inputs;
globalConfig = config.ghaf.global-config;
hostConfig = lib.ghaf.vm.mkHostConfig { ... };
};

The extendModules function allows composing configurations lazily. This is crucial for Ghaf because:

  1. Lazy evaluation: VM configs aren’t evaluated until needed
  2. Composition: Downstream can extend without copying
  3. No circular deps: Host can reference VM config safely
# Base configuration from profile
baseConfig = profileConfig.guivmBase;
# Extend with target-specific settings
evaluatedConfig = baseConfig.extendModules {
modules = [
{ environment.systemPackages = [ pkgs.myApp ]; }
];
};

Each VM module has an evaluatedConfig option that receives the fully-evaluated NixOS configuration for that VM. This is what microvm.vms consumes to create the actual VM.

options.ghaf.virtualization.microvm.guivm = {
enable = lib.mkEnableOption "GUI VM";
evaluatedConfig = lib.mkOption {
type = lib.types.nullOr lib.types.unspecified;
default = null;
description = "Pre-evaluated NixOS configuration for GUI VM";
};
};