Downstream Project Setup
Downstream Project Setup
Section titled “Downstream Project Setup”This guide covers how to set up a downstream project that builds on the Ghaf Framework, enabling full customization while staying updated with upstream changes.
Overview
Section titled “Overview”A downstream project typically:
- Imports Ghaf as a flake input
- Extends or replaces VM configurations
- Adds custom applications and services
- Creates product-specific targets
Example downstream projects:
- ghaf-fmo-laptop - Field Mission Operations variant
- Custom product builds based on Ghaf
Project Structure
Section titled “Project Structure”Recommended directory structure for a downstream project:
my-ghaf-product/├── flake.nix # Main flake with ghaf input├── flake.lock # Locked dependencies├── lib/│ └── profiles.nix # Custom profiles├── modules/│ ├── custom-vm/ # Custom VM definitions│ │ └── my-vm-base.nix│ ├── services/ # Product-specific services│ │ └── my-service.nix│ └── hardware/ # Custom hardware support│ └── my-device.nix├── targets/│ └── my-product/│ └── flake-module.nix # Product configurations└── README.mdBasic Setup
Section titled “Basic Setup”Step 1: Create flake.nix
Section titled “Step 1: Create flake.nix”{ description = "My Ghaf-based Product";
inputs = { # Pin to specific Ghaf version or branch ghaf = { url = "github:tiiuae/ghaf"; # Or pin to specific commit: # url = "github:tiiuae/ghaf/abc123"; };
# Follow Ghaf's nixpkgs nixpkgs.follows = "ghaf/nixpkgs";
# Flake-parts for module organization flake-parts.follows = "ghaf/flake-parts"; };
outputs = inputs@{ flake-parts, ghaf, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" "aarch64-linux" ];
imports = [ # Import Ghaf's flake modules ghaf.flakeModules.targets
# Your target configurations ./targets/my-product/flake-module.nix ];
# Expose custom modules flake.nixosModules = { my-service = ./modules/services/my-service.nix; my-vm-base = ./modules/custom-vm/my-vm-base.nix; my-hardware = ./modules/hardware/my-device.nix; }; };}Step 2: Create Target Configuration
Section titled “Step 2: Create Target Configuration”{ inputs, lib, ... }:let # Access Ghaf's builders mkGhafConfiguration = inputs.ghaf.lib.ghaf.builders.mkGhafConfiguration; mkGhafInstaller = inputs.ghaf.lib.ghaf.builders.mkGhafInstaller;in{ flake.nixosConfigurations = { # Debug variant my-product-debug = mkGhafConfiguration { name = "my-product"; system = "x86_64-linux"; profile = "laptop-x86";
# Use Ghaf's hardware definition or your own hardwareModule = inputs.ghaf.nixosModules.hardware-lenovo-x1-carbon-gen11;
# Debug variant auto-applies lib.ghaf.profiles.debug via mkDefault variant = "debug";
# Override specific global config values extraConfig = { global-config.features.bluetooth.enable = false; };
# Additional modules extraModules = [ inputs.self.nixosModules.my-service { # Inline configuration environment.systemPackages = [ inputs.nixpkgs.vim ]; } ]; };
# Release variant my-product-release = mkGhafConfiguration { name = "my-product"; system = "x86_64-linux"; profile = "laptop-x86"; hardwareModule = inputs.ghaf.nixosModules.hardware-lenovo-x1-carbon-gen11; variant = "release"; }; };
# Installer flake.packages.x86_64-linux.my-product-installer = mkGhafInstaller { name = "my-product"; configuration = inputs.self.nixosConfigurations.my-product-release; };}Customizing VMs
Section titled “Customizing VMs”Extending GUI VM
Section titled “Extending GUI VM”# In your target configuration{ config, lib, inputs, ... }:let # Get base GUI VM from profile baseGuivm = config.ghaf.profiles.laptop-x86.guivmBase;
# Extend with your customizations myGuivm = baseGuivm.extendModules { modules = [ # Add your applications ({ pkgs, ... }: { environment.systemPackages = with pkgs; [ my-custom-app my-monitoring-tool ]; })
# Configure services { services.myProductService = { enable = true; apiEndpoint = "https://my-api.example.com"; }; }
# Desktop customization { services.xserver.desktopManager.gnome.enable = lib.mkForce false; services.xserver.desktopManager.plasma5.enable = true; } ]; };in{ # Use your extended GUI VM (pass the full system, not .config) ghaf.virtualization.microvm.guivm.evaluatedConfig = myGuivm;}Creating Custom VMs
Section titled “Creating Custom VMs”{ config, lib, pkgs, globalConfig, hostConfig, inputs, ... }:{ _file = ./gcs-vm-base.nix;
imports = [ # Import Ghaf's base VM modules inputs.ghaf.nixosModules.vm-config inputs.ghaf.nixosModules.givc ];
# Standard VM setup networking.hostName = hostConfig.vmName;
ghaf = { profiles.debug.enable = globalConfig.debug.enable; givc.enable = globalConfig.givc.enable; };
# Your GCS-specific configuration services.gcsClient = { enable = true; serverAddress = "gcs.example.com"; };
environment.systemPackages = with pkgs; [ gcs-tools mission-planner ];}Adding Application VMs
Section titled “Adding Application VMs”{ config, ... }:let mkAppVm = config.ghaf.profiles.laptop-x86.mkAppVm;in{ ghaf.virtualization.microvm.appvm.vms = { # Custom productivity app my-productivity = mkAppVm { name = "my-productivity"; applications = [{ name = "My Productivity Suite"; description = "Company productivity tools"; packages = [ pkgs.my-productivity ]; icon = "my-productivity"; command = "my-productivity"; }]; extraModules = [{ networking.proxy.default = "http://proxy.example.com:8080"; }]; };
# Secure browser secure-browser = mkAppVm { name = "secure-browser"; applications = [{ name = "Secure Browser"; description = "Isolated web browsing"; packages = [ pkgs.firefox ]; icon = "firefox"; command = "firefox --private-window"; }]; }; };}Managing Features
Section titled “Managing Features”Reassigning Features
Section titled “Reassigning Features”{ ghaf.global-config.features = { # Move fingerprint to admin VM for enrollment only fprint.targetVms = [ "admin-vm" ];
# Enable YubiKey in custom VM yubikey.targetVms = [ "gui-vm" "gcs-vm" ];
# Disable unused features bluetooth.enable = false; };}Adding Custom Features
Section titled “Adding Custom Features”Define new features in your downstream project:
{ config, lib, globalConfig, ... }:let enabled = globalConfig.features.gcsRadio.enable or false;in{ _file = ./gcs-radio.nix;
config = lib.mkIf enabled { services.gcsRadio = { enable = true; frequency = globalConfig.features.gcsRadio.frequency or 915; };
hardware.firmware = [ pkgs.gcs-radio-firmware ]; };}Updating from Upstream
Section titled “Updating from Upstream”Update Ghaf Input
Section titled “Update Ghaf Input”# Update to latestnix flake update ghaf
# Or update to specific commitnix flake lock --update-input ghaf --override-input ghaf github:tiiuae/ghaf/abc123Check for Breaking Changes
Section titled “Check for Breaking Changes”- Read Ghaf release notes
- Check for API changes in
lib.ghaf.* - Test builds before deploying:
nix build .#my-product-debug --dry-runnix build .#my-product-releaseHandle Breaking Changes
Section titled “Handle Breaking Changes”If upstream changes break your configuration:
# Temporarily override in flake.nixinputs.ghaf = { url = "github:tiiuae/ghaf"; # Pin to last working version # url = "github:tiiuae/ghaf/known-good-commit";};Best Practices
Section titled “Best Practices”1. Minimize Upstream Modifications
Section titled “1. Minimize Upstream Modifications”Prefer extending over replacing:
# Good - extend basebaseGuivm.extendModules { modules = [ ... ]; }
# Avoid - replace entirely (loses upstream updates)lib.nixosSystem { modules = [ my-complete-guivm ]; }2. Use Variant for Profile Selection
Section titled “2. Use Variant for Profile Selection”# Good - use variant parametermkGhafConfiguration { variant = "debug"; # Overrides go in extraConfig extraConfig = { global-config.features.bluetooth.enable = false; };};
# Avoid - define everything manuallyextraConfig = { global-config = { debug.enable = true; development.ssh.daemon.enable = true; # ... copying all profile values };};3. Keep Hardware Separate
Section titled “3. Keep Hardware Separate”# Good - hardware in separate modulehardwareModule = inputs.self.nixosModules.my-hardware;
# Avoid - inline hardware in targetextraModules = [{ hardware.cpu.intel.enable = true; /* ... */ }];4. Document Customizations
Section titled “4. Document Customizations”# SPDX-FileCopyrightText: 2026 My Company# SPDX-License-Identifier: Apache-2.0## My Product Service## This service provides XYZ functionality for our product.# It depends on Ghaf's GIVC for inter-VM communication.#{ config, lib, ... }: ...Troubleshooting
Section titled “Troubleshooting”Build Failures After Update
Section titled “Build Failures After Update”# Show detailed tracenix build .#my-product-debug --show-trace
# Check specific optionnix eval .#nixosConfigurations.my-product-debug.config.ghaf.global-config --json | jqVM Not Starting
Section titled “VM Not Starting”# Check VM configurationnix eval .#nixosConfigurations.my-product-debug.config.microvm.vms.gui-vm.config.config.networking.hostName
# Build VM systemnix build .#nixosConfigurations.my-product-debug.config.microvm.vms.gui-vm.config.config.system.build.toplevelFeature Not Working
Section titled “Feature Not Working”# Verify feature assignmentnix eval .#nixosConfigurations.my-product-debug.config.ghaf.global-config.features.myFeature
# Check if service enabled in VMnix eval .#nixosConfigurations.my-product-debug.config.microvm.vms.gui-vm.config.config.services.myService.enableChecklist for Downstream Projects
Section titled “Checklist for Downstream Projects”- Create flake.nix with ghaf input
- Follow ghaf/nixpkgs to avoid conflicts
- Create target configuration using mkGhafConfiguration
- Use extendModules for VM customization
- Keep hardware definitions separate
- Document customizations
- Test both debug and release variants
- Set up CI for build testing
- Plan update strategy for upstream changes
See Also
Section titled “See Also”- Extending Targets - Target customization
- Creating VMs - New VM types
- Migration Guide - Migrating from legacy patterns
- VM Composition - Architecture overview