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:

AspectLegacy PatternModern Pattern
Host-VM communicationconfigHost = config;globalConfig + hostConfig via specialArgs
VM customizationextraModules optionextendModules on VM bases
Settings propagationManual threadingspecialArgs injection
Feature assignmentHardcoded in VMsglobalConfig.features with targetVms
Profile selectionDebug flag checkslib.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

RemovedReplacement
ghaf.virtualization.microvm.*.extraModulesUse extendModules on VM base
modules.nix feature optionsglobalConfig.features.*
lib.ghaf.mkVmSpecialArgslib.ghaf.vm.mkSpecialArgs
lib.ghaf.mkVmHostConfiglib.ghaf.vm.mkHostConfig
lib.ghaf.getVmConfiglib.ghaf.vm.getConfig
ChangeImpact
VMs require evaluatedConfigMust set via profile or extendModules
Features centralizedMove feature config to globalConfig.features
Profiles simplifiedUse 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