Configuration Propagation
Configuration Propagation
Section titled “Configuration Propagation”This guide explains how configuration values flow from the host to VMs in Ghaf using the globalConfig and hostConfig patterns via specialArgs.
The Problem We Solved
Section titled “The Problem We Solved”In earlier versions of Ghaf, VMs accessed host configuration directly:
# OLD PATTERN - DON'T USE{ configHost }:{ config = { # Direct reference to host config ghaf.profiles.debug.enable = configHost.ghaf.profiles.debug.enable; networking.hostName = configHost.ghaf.networking.vms.gui-vm.name; };}This caused several problems:
- Tight coupling: VM modules depended on specific host config structure
- Circular dependencies: Host needs VM config, VM needs host config
- Hard to test: Can’t evaluate VM modules without full host config
- Difficult downstream: Hard to override or extend
- Unclear data flow: Where does each value come from?
The Solution: specialArgs
Section titled “The Solution: specialArgs”Ghaf now uses specialArgs to inject configuration into VM modules:
# NEW PATTERN{ config, lib, globalConfig, hostConfig, ... }:{ config = { # Clear separation of concerns ghaf.profiles.debug.enable = globalConfig.debug.enable; networking.hostName = hostConfig.vmName; };}What Are specialArgs?
Section titled “What Are specialArgs?”When evaluating NixOS modules with evalModules, specialArgs provides values that are automatically available to all modules:
lib.nixosSystem { specialArgs = { # These become function parameters in ALL modules globalConfig = { /* ... */ }; hostConfig = { /* ... */ }; inputs = { /* ... */ }; }; modules = [ ... ];}Unlike regular module arguments, specialArgs:
- Don’t need to be explicitly passed between modules
- Are available everywhere in the module tree
- Don’t create dependencies between modules
globalConfig
Section titled “globalConfig”globalConfig contains settings that should be identical across all VMs.
What Goes in globalConfig?
Section titled “What Goes in globalConfig?”| Category | Examples | Why Global? |
|---|---|---|
| Debug settings | debug.enable | Same debug state everywhere |
| Development | ssh.daemon.enable, debug.tools.enable | Consistent dev experience |
| Logging | logging.enable, logging.listener | Centralized log collection |
| GIVC | givc.enable, givc.debug | Inter-VM communication config |
| Security | security.audit.enable | Consistent security posture |
| Features | features.fprint, features.wifi | Hardware feature assignments |
Accessing globalConfig
Section titled “Accessing globalConfig”In VM modules, globalConfig is available as a function parameter:
{ config, lib, pkgs, globalConfig, # Injected via specialArgs ...}:{ config = { # Access global settings ghaf.profiles.debug.enable = globalConfig.debug.enable;
ghaf.development = { ssh.daemon.enable = globalConfig.development.ssh.daemon.enable; debug.tools.enable = globalConfig.development.debug.tools.enable; };
logging = { inherit (globalConfig.logging) enable listener; client.enable = globalConfig.logging.enable; };
ghaf.givc = { enable = globalConfig.givc.enable; debug = globalConfig.givc.debug; }; };}Setting globalConfig
Section titled “Setting globalConfig”At the host level, set ghaf.global-config:
# Using a predefined profile{ lib, ... }:{ ghaf.global-config = lib.ghaf.profiles.debug;}
# Or customize{ lib, ... }:{ ghaf.global-config = lib.ghaf.mkGlobalConfig "debug" { storage.encryption.enable = true; features.bluetooth.enable = true; };}
# Or set directly{ ghaf.global-config = { debug.enable = true; development.ssh.daemon.enable = true; logging.enable = true; givc.enable = true; features = { wifi.enable = true; wifi.targetVms = [ "net-vm" ]; }; };}globalConfig Schema
Section titled “globalConfig Schema”globalConfig = { # Debug mode debug.enable = false;
# Development settings development = { ssh.daemon.enable = false; debug.tools.enable = false; nix-setup.enable = false; };
# Logging logging = { enable = false; listener = { address = ""; # Auto-populated from admin-vm IP port = 9999; }; server.endpoint = ""; };
# Security security.audit.enable = false;
# GIVC givc = { enable = false; debug = false; };
# Services services = { power-manager.enable = false; performance.enable = false; };
# Storage storage = { encryption.enable = false; storeOnDisk = false; };
# Shared memory shm = { enable = false; serverSocketPath = ""; };
# IDS VM idsvm.mitmproxy.enable = false;
# Platform (auto-populated from host) platform = { buildSystem = "x86_64-linux"; hostSystem = "x86_64-linux"; timeZone = "UTC"; };
# Features (hardware feature assignments) features = { fprint = { enable = true; targetVms = [ "gui-vm" ]; }; yubikey = { enable = true; targetVms = [ "gui-vm" ]; }; brightness = { enable = true; targetVms = [ "gui-vm" ]; }; wifi = { enable = true; targetVms = [ "net-vm" ]; }; audio = { enable = true; targetVms = [ "audio-vm" ]; }; bluetooth = { enable = true; targetVms = [ "audio-vm" ]; }; };};hostConfig
Section titled “hostConfig”hostConfig contains settings that are different for each VM but derived from the host configuration.
What Goes in hostConfig?
Section titled “What Goes in hostConfig?”| Category | Examples | Why Per-VM? |
|---|---|---|
| Identity | vmName, vmType | Each VM has unique name |
| Networking | ipv4, mac, hosts | Different IPs per VM |
| Users | user configuration | May vary by VM role |
| Hardware | kernel, passthrough | Device-specific |
| GIVC | cliArgs, enableTls | VM-specific IPC config |
Accessing hostConfig
Section titled “Accessing hostConfig”In VM modules, hostConfig is available as a function parameter:
{ config, lib, hostConfig, # Injected via specialArgs ...}:{ config = { # VM identity networking.hostName = hostConfig.vmName;
# Network settings specific to this VM networking.interfaces.ethint0.ipv4.addresses = [{ address = hostConfig.networking.thisVm.ipv4; prefixLength = 24; }];
# User configuration (may differ per VM) users.users = hostConfig.users.managed;
# Reference other VMs' network info networking.hosts = hostConfig.networking.hosts; };}Creating hostConfig
Section titled “Creating hostConfig”Use lib.ghaf.vm.mkHostConfig to create hostConfig for a VM:
# In profile or targethostConfig = lib.ghaf.vm.mkHostConfig { config = config; # Host NixOS config vmName = "gui-vm"; extraConfig = { # Additional settings if needed customOption = "value"; };};hostConfig Schema
Section titled “hostConfig Schema”hostConfig = { # VM identity vmName = "gui-vm"; vmType = "guivm"; # vmName with dashes removed
# Kernel configuration (if VM type has one) kernel = { /* ghaf.kernel.<vmType> or null */ }; # QEMU configuration (if VM type has one) qemu = { /* ghaf.qemu.<vmType> or null */ };
# Hardware passthrough passthrough = { qemuExtraArgs = [ /* extra QEMU args for this VM */ ]; vmUdevExtraRules = "..."; # Udev rules for passthrough };
# Host filesystem paths sharedVmDirectory = "/...";
# Boot configuration microvmBoot = { enable = bool; };
# Hardware devices hardware = { devices = { /* ghaf.hardware.devices */ }; };
# Common namespace (killswitch, etc.) common = { /* ghaf.common config from host */ };
# User configuration users = { /* ghaf.users config from host */ };
# Reference services reference = { services = { /* ghaf.reference.services */ }; };
# Networking networking = { # All VM network configs (for /etc/hosts, etc.) hosts = { "gui-vm" = { ipv4 = "192.168.100.3"; mac = "..."; }; "net-vm" = { ipv4 = "192.168.100.1"; mac = "..."; }; # ... }; # This VM's specific config thisVm = { ipv4 = "192.168.100.3"; mac = "02:00:00:00:00:03"; # ... }; };
# GIVC configuration givc = { cliArgs = "--addr ..."; enableTls = bool; };
# Security settings security = { sshKeys = { /* SSH key configuration */ }; };
# AppVM configurations (for launcher generation) appvms = { /* enabledVms with derived values */ };
# GUI VM applications guivm = { applications = [ /* GUI VM local applications */ ]; };};How specialArgs Are Injected
Section titled “How specialArgs Are Injected”In Profiles
Section titled “In Profiles”Profiles create the specialArgs when defining VM bases:
{ config, lib, inputs, ... }:{ options.ghaf.profiles.laptop-x86.guivmBase = lib.mkOption { type = lib.types.unspecified; readOnly = true; default = lib.nixosSystem { # This is where specialArgs are created specialArgs = lib.ghaf.vm.mkSpecialArgs { inherit lib inputs; globalConfig = config.ghaf.global-config; hostConfig = lib.ghaf.vm.mkHostConfig { inherit config; vmName = "gui-vm"; }; }; modules = [ inputs.self.nixosModules.guivm-base ]; }; };}lib.ghaf.vm.mkSpecialArgs
Section titled “lib.ghaf.vm.mkSpecialArgs”This helper creates the specialArgs attrset:
# UsagespecialArgs = lib.ghaf.vm.mkSpecialArgs { lib = extendedLib; # Required: lib with ghaf functions inputs = flakeInputs; # Required: flake inputs globalConfig = config.ghaf.global-config; # Required hostConfig = hostConfigValue; # Optional: from mkHostConfig extraArgs = { myArg = value; }; # Optional: additional args};
# Result{ lib = extendedLib; inputs = flakeInputs; globalConfig = { /* ... */ }; hostConfig = { /* ... */ }; # If provided myArg = value; # If extraArgs provided}Comparison: Old vs New
Section titled “Comparison: Old vs New”Old Pattern (configHost)
Section titled “Old Pattern (configHost)”# Module received entire host config{ configHost }:{ # Had to navigate host config structure ghaf.profiles.debug.enable = configHost.ghaf.profiles.debug.enable;
# Tight coupling to host config layout networking.hostName = configHost.ghaf.networking.vms.gui-vm.name;
# Repeated everywhere ghaf.development.ssh.daemon.enable = configHost.ghaf.development.ssh.daemon.enable;}Problems:
- Module depends on full host config structure
- Changes to host config break VM modules
- Hard to test VM modules in isolation
- Downstream can’t easily override values
New Pattern (globalConfig/hostConfig)
Section titled “New Pattern (globalConfig/hostConfig)”# Module receives only what it needs{ globalConfig, hostConfig, ... }:{ # Clear, direct access ghaf.profiles.debug.enable = globalConfig.debug.enable;
# VM-specific from hostConfig networking.hostName = hostConfig.vmName;
# Same pattern everywhere ghaf.development.ssh.daemon.enable = globalConfig.development.ssh.daemon.enable;}Benefits:
- Module only depends on documented interface
- Host config changes don’t break VM modules
- Easy to test with mock globalConfig/hostConfig
- Downstream can customize at well-defined points
Best Practices
Section titled “Best Practices”1. Use globalConfig for Shared Settings
Section titled “1. Use globalConfig for Shared Settings”# GOOD: Setting applies to all VMsghaf.logging.enable = globalConfig.logging.enable;
# BAD: Hardcoded per-VMghaf.logging.enable = true;2. Use hostConfig for VM-Specific Settings
Section titled “2. Use hostConfig for VM-Specific Settings”# GOOD: Each VM gets its own valuenetworking.hostName = hostConfig.vmName;
# BAD: Hardcodednetworking.hostName = "gui-vm";3. Use Features for Optional Hardware
Section titled “3. Use Features for Optional Hardware”# GOOD: Centrally managed, can be reassignedconfig = lib.mkIf (lib.ghaf.features.isEnabledFor globalConfig "fprint" "gui-vm") { services.fprintd.enable = true;};
# BAD: Hardcoded in one VMconfig = { services.fprintd.enable = true; # What if we want it elsewhere?};4. Provide Defaults
Section titled “4. Provide Defaults”# GOOD: Safe access with fallbacknetworking.timeZone = globalConfig.platform.timeZone or "UTC";
# RISKY: Might fail if not setnetworking.timeZone = globalConfig.platform.timeZone;5. Document What You Use
Section titled “5. Document What You Use”{ config, lib, pkgs, globalConfig, # Uses: debug.enable, logging.*, givc.* hostConfig, # Uses: vmName, networking.thisVm ...}:Summary
Section titled “Summary”| Concept | Purpose | Scope | Access |
|---|---|---|---|
globalConfig | Settings same for all VMs | Global | globalConfig.setting |
hostConfig | Settings different per VM | Per-VM | hostConfig.setting |
specialArgs | Injection mechanism | All modules | Function parameters |
The separation provides:
- Clear data flow: Know where each value comes from
- Loose coupling: Modules don’t depend on host structure
- Testability: Mock specialArgs for testing
- Extensibility: Override at well-defined points
For more details:
- lib.ghaf.vm API Reference - Helper functions
- lib.ghaf.features API Reference - Feature system
- Module Conventions - Writing modules correctly