Skip to content

Migration Guide

This guide helps migrate code from legacy Ghaf patterns to the modern composition-based architecture.

The Ghaf architecture has been significantly refactored to improve composability and maintainability:

Aspect Legacy Pattern Modern Pattern
Host-VM communication configHost = config; globalConfig + hostConfig via specialArgs
VM customization extraModules option extendModules on VM bases
Settings propagation Manual threading specialArgs injection
Feature assignment Hardcoded in VMs globalConfig.features with targetVms
Profile selection Debug flag checks lib.ghaf.profiles.*

Legacy:

{ configHost }:
let
cfg = configHost.ghaf.virtualization.microvm.guivm;
in
{
config = {
ghaf.profiles.debug.enable = configHost.ghaf.profiles.debug.enable;
};
}

Modern:

{ config, lib, globalConfig, hostConfig, ... }:
{
_file = ./my-module.nix;
config = {
ghaf.profiles.debug.enable = globalConfig.debug.enable;
networking.hostName = hostConfig.vmName;
};
}

Legacy:

# Inline in guivm.nix
guivmBaseConfiguration = {
imports = [ ... ];
config = {
ghaf.profiles.debug.enable = configHost.ghaf.profiles.debug.enable;
networking.hostName = "gui-vm";
# ... hundreds of lines of config
};
};
microvm.vms.gui-vm.config = guivmBaseConfiguration // {
imports = guivmBaseConfiguration.imports ++ cfg.extraModules;
};

Modern:

# In guivm-base.nix (separate file)
{ globalConfig, hostConfig, ... }:
{
_file = ./guivm-base.nix;
ghaf.profiles.debug.enable = globalConfig.debug.enable;
networking.hostName = hostConfig.vmName;
# ... config with specialArgs access
}
# In profile (laptop-x86.nix)
guivmBase = lib.nixosSystem {
specialArgs = lib.ghaf.vm.mkSpecialArgs {
inherit lib inputs;
globalConfig = config.ghaf.global-config;
hostConfig = lib.ghaf.vm.mkHostConfig {
inherit config;
vmName = "gui-vm";
};
};
modules = [ guivm-base hardwareExtras ];
};
# In guivm.nix - wire up using evaluatedConfig
config = lib.mkIf cfg.enable {
microvm.vms.gui-vm.evaluatedConfig = cfg.evaluatedConfig;
};

Legacy:

# In modules.nix
ghaf.virtualization.microvm.guivm.extraModules = [
firmwareModule
servicesModule
desktopModule
];
# In target
ghaf.virtualization.microvm.guivm.extraModules = lib.mkAfter [
myCustomModule
];

Modern:

# In profile - compose base with hardware modules
guivmBase = lib.nixosSystem {
specialArgs = ...;
modules = [
guivm-base
hardware.definition.guivm.extraModules
# Feature modules auto-included via imports
];
};
# In target - use extendModules
let
baseVm = config.ghaf.profiles.laptop-x86.guivmBase;
extendedVm = baseVm.extendModules {
modules = [ myCustomModule ];
};
in
{
# Pass the full system result, not .config
ghaf.virtualization.microvm.guivm.evaluatedConfig = extendedVm;
}

Legacy:

# Hardcoded in guivm-base.nix
services.fprintd.enable = true;
# Or with option
services.fprintd.enable = cfg.features.fprint.enable;

Modern:

# In global config
ghaf.global-config.features.fprint = {
enable = true;
targetVms = [ "gui-vm" ];
};
# In VM base
services.fprintd.enable =
lib.ghaf.features.isEnabledFor globalConfig "fprint" "gui-vm";

Legacy:

# In target
{ ... }:
let
variant = "debug";
in
{
# Manual debug/release logic everywhere
ghaf.profiles.debug.enable = variant == "debug";
ghaf.development.ssh.daemon.enable = variant == "debug";
# ... repeated for every option
}

Modern:

# In target - use variant parameter
mkGhafConfiguration {
name = "my-target";
system = "x86_64-linux";
profile = "laptop-x86";
hardwareModule = self.nixosModules.hardware-my-target;
variant = "debug"; # Auto-applies lib.ghaf.profiles.debug via mkDefault
}
# Or with customization via extraConfig
mkGhafConfiguration {
# ... params ...
variant = "debug";
extraConfig = {
global-config.features.bluetooth.enable = false;
};
};

Legacy (Anti-pattern):

module-a.nix
{ inputs }:
{
imports = [ (import ./module-b.nix { inherit inputs; }) ];
}
# module-b.nix
{ inputs }:
{
config = {
something = inputs.self.packages.x86_64-linux.foo;
};
}

Modern:

# In evaluation (profile or target)
lib.nixosSystem {
specialArgs = { inherit inputs lib; };
modules = [ ./module-a.nix ./module-b.nix ];
}
# In module-b.nix
{ inputs, ... }: # Available from specialArgs
{
config.something = inputs.self.packages.x86_64-linux.foo;
}

Legacy:

# VM module
{ configHost }:
{
services.foo.hostAddress = configHost.ghaf.common.extraNetworking.hostAddress;
services.foo.debug = configHost.ghaf.profiles.debug.enable;
}

Modern:

# VM module
{ globalConfig, hostConfig, ... }:
{
services.foo.hostAddress = hostConfig.networking.hostAddress;
services.foo.debug = globalConfig.debug.enable;
}

Legacy:

# In modules.nix
options.ghaf.virtualization.microvm.guivm.features.fprint.enable = ...;
# In guivm.nix
services.fprintd.enable = cfg.features.fprint.enable;
# In profile
ghaf.virtualization.microvm.guivm.features.fprint.enable = true;

Modern:

# In lib/global-config.nix
features.fprint = {
enable = mkEnableOption "fingerprint" // { default = true; };
targetVms = mkOption { default = [ "gui-vm" ]; };
};
# In guivm-base.nix
services.fprintd.enable =
lib.ghaf.features.isEnabledFor globalConfig "fprint" "gui-vm";
# In target (to customize)
ghaf.global-config.features.fprint.targetVms = [ "admin-vm" ];

flake.nix
{
outputs = { ghaf, ... }:
let
laptop-configuration = ghaf.builders.laptop-configuration;
in
{
nixosConfigurations.my-product = laptop-configuration "lenovo-x1" "debug" [
./hardware.nix
({ config, ... }: {
# Customize via extraModules option
ghaf.virtualization.microvm.guivm.extraModules = [
./my-gui-customizations.nix
];
})
];
};
}
flake.nix
{
outputs = { ghaf, ... }:
let
mkGhafConfiguration = ghaf.lib.ghaf.builders.mkGhafConfiguration;
in
{
nixosConfigurations.my-product-debug = mkGhafConfiguration {
name = "my-product";
system = "x86_64-linux";
profile = "laptop-x86";
hardwareModule = ghaf.nixosModules.hardware-lenovo-x1-carbon-gen11;
variant = "debug";
extraModules = [
({ config, ... }:
let
baseVm = config.ghaf.profiles.laptop-x86.guivmBase;
extendedVm = baseVm.extendModules {
modules = [ ./my-gui-customizations.nix ];
};
in
{
# Pass the full system result, not .config
ghaf.virtualization.microvm.guivm.evaluatedConfig = extendedVm;
})
];
};
};
}

For each module you’re migrating:

  • Add _file = ./module-name.nix; as first attribute
  • Replace { configHost }: with { config, lib, globalConfig, hostConfig, ... }:
  • Replace configHost.ghaf.profiles.debug.enable with globalConfig.debug.enable
  • Replace hardcoded VM names with hostConfig.vmName
  • Replace configHost.ghaf.common.extraNetworking.* with hostConfig.networking.*
  • Replace feature checks with lib.ghaf.features.isEnabledFor
  • Remove { inputs }: wrapper if present (use specialArgs instead)
  • Add proper imports from inputs
  • Use lib.mkDefault for overridable values
  • Test build with nix build .#target --dry-run

Removed Replacement
ghaf.virtualization.microvm.*.extraModules Use extendModules on VM base
modules.nix feature options globalConfig.features.*
lib.ghaf.mkVmSpecialArgs lib.ghaf.vm.mkSpecialArgs
lib.ghaf.mkVmHostConfig lib.ghaf.vm.mkHostConfig
lib.ghaf.getVmConfig lib.ghaf.vm.getConfig
Change Impact
VMs require evaluatedConfig Must set via profile or extendModules
Features centralized Move feature config to globalConfig.features
Profiles simplified Use lib.ghaf.profiles.* for common presets

If you encounter issues migrating:

  1. Check the Architecture Documentation
  2. Review Anti-Patterns for common mistakes
  3. Look at existing modules for examples
  4. Open an issue on GitHub with specific error messages