Writing Modules
Writing Modules
Section titled “Writing Modules”This guide covers how to write NixOS modules for the Ghaf Framework, following established conventions and best practices.
Module Structure
Section titled “Module Structure”Every Ghaf module should follow this template:
# SPDX-FileCopyrightText: 2022-2026 TII (SSRC) and the Ghaf contributors{ config, lib, pkgs, ... }:let cfg = config.ghaf.myModule;in{ _file = ./my-module.nix;
options.ghaf.myModule = { enable = lib.mkEnableOption "my module description";
# Additional options... };
config = lib.mkIf cfg.enable { # Configuration when enabled... };}Key Elements
Section titled “Key Elements”- SPDX Header - License information at the top
- Module Arguments -
{ config, lib, pkgs, ... }: - Local Bindings -
let cfg = ... infor readability _fileDeclaration - For error tracing (first attribute!)- Options - Under
options.ghaf.* - Config - Under
config, usually withmkIf
Module Categories
Section titled “Module Categories”Host-Only Modules
Section titled “Host-Only Modules”Modules that run on the host system:
{ config, lib, ... }:let cfg = config.ghaf.services.myHostService;in{ _file = ./my-host-service.nix;
options.ghaf.services.myHostService = { enable = lib.mkEnableOption "my host service"; };
config = lib.mkIf cfg.enable { systemd.services.my-host-service = { description = "My Host Service"; wantedBy = [ "multi-user.target" ]; serviceConfig.ExecStart = "${pkgs.my-tool}/bin/my-tool"; }; };}VM Base Modules
Section titled “VM Base Modules”Modules for VM base configurations receive globalConfig and hostConfig:
{ config, lib, pkgs, globalConfig, hostConfig, ... }:{ _file = ./my-vm-base.nix;
# Access global settings ghaf.profiles.debug.enable = globalConfig.debug.enable;
# Access VM-specific settings networking.hostName = hostConfig.vmName;
# VM configuration services.myService.enable = true;}Feature Modules
Section titled “Feature Modules”Modules that implement hardware features:
{ config, lib, pkgs, globalConfig, ... }:let featureEnabled = lib.ghaf.features.isEnabledFor globalConfig "myFeature" "gui-vm";in{ _file = ./my-feature.nix;
config = lib.mkIf featureEnabled { services.myFeature.enable = true; };}Options Best Practices
Section titled “Options Best Practices”Use mkEnableOption
Section titled “Use mkEnableOption”For boolean enable flags:
options.ghaf.myFeature = { # Good enable = lib.mkEnableOption "my feature description";
# Avoid (less discoverable) enabled = lib.mkOption { type = lib.types.bool; default = false; };};Use mkOption with Types
Section titled “Use mkOption with Types”For other options, always specify types:
options.ghaf.myFeature = { port = lib.mkOption { type = lib.types.port; default = 8080; description = "Port for my service"; };
users = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; description = "Users allowed to access the feature"; };
config = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = {}; description = "Additional configuration"; };};Use mkDefault for Overridable Defaults
Section titled “Use mkDefault for Overridable Defaults”When setting values that downstream should be able to override:
config = lib.mkIf cfg.enable { # Overridable default - downstream can change with normal priority services.openssh.enable = lib.mkDefault true;
# Fixed value - requires mkForce to override security.audit.enable = true;};Document Options
Section titled “Document Options”Every option should have a description:
options.ghaf.myFeature.timeout = lib.mkOption { type = lib.types.int; default = 30; description = '' Timeout in seconds for my feature.
Set to 0 to disable timeout entirely. ''; example = 60;};Config Best Practices
Section titled “Config Best Practices”Use mkIf for Conditional Config
Section titled “Use mkIf for Conditional Config”config = lib.mkIf cfg.enable { services.foo.enable = true;};Use mkMerge for Multiple Conditions
Section titled “Use mkMerge for Multiple Conditions”config = lib.mkMerge [ # Always applied { environment.variables.MY_VAR = "value"; }
# Only when feature A enabled (lib.mkIf cfg.featureA.enable { services.a.enable = true; })
# Only when feature B enabled (lib.mkIf cfg.featureB.enable { services.b.enable = true; })];Avoid mkForce Unless Necessary
Section titled “Avoid mkForce Unless Necessary”# Avoid - prevents downstream overridesservices.ssh.enable = lib.mkForce true;
# Better - allows overridesservices.ssh.enable = lib.mkDefault true;Accessing Configuration
Section titled “Accessing Configuration”From Host Module
Section titled “From Host Module”{ config, ... }:{ config = { # Access other ghaf options services.foo.port = config.ghaf.networking.basePort + 1;
# Access global config something = config.ghaf.global-config.debug.enable; };}From VM Module (via specialArgs)
Section titled “From VM Module (via specialArgs)”{ config, globalConfig, hostConfig, ... }:{ config = { # Access global settings ghaf.profiles.debug.enable = globalConfig.debug.enable;
# Access VM-specific settings networking.hostName = hostConfig.vmName;
# Access networking for other VMs services.foo.guivmAddress = hostConfig.networking.guivm.ipv4; };}File Organization
Section titled “File Organization”Module Placement
Section titled “Module Placement”| Type | Location | Example |
|---|---|---|
| Common/shared | modules/common/ | modules/common/networking.nix |
| Host services | modules/common/services/ | modules/common/services/power.nix |
| Hardware | modules/hardware/ | modules/hardware/lenovo/x1.nix |
| Desktop | modules/desktop/ | modules/desktop/cosmic.nix |
| VM infrastructure | modules/microvm/ | modules/microvm/host/microvm-host.nix |
| System VM bases | modules/microvm/sysvms/ | modules/microvm/sysvms/guivm-base.nix |
| VM features | modules/microvm/*-features/ | modules/microvm/guivm-features/fprint.nix |
| Profiles | modules/profiles/ | modules/profiles/laptop-x86.nix |
| Reference impl | modules/reference/ | modules/reference/appvms/chrome.nix |
Exporting Modules
Section titled “Exporting Modules”Add modules to the appropriate flake-module.nix:
{ flake.nixosModules = { my-new-module = ./my-new-module.nix; };}Testing Modules
Section titled “Testing Modules”Build Validation
Section titled “Build Validation”# Quick syntax checknix-instantiate --parse path/to/module.nix
# Evaluate in contextnix build .#nixosConfigurations.lenovo-x1-carbon-gen11-debug.config.system.build.toplevel --dry-runDebug Evaluation
Section titled “Debug Evaluation”# Show trace on errorsnix build .#target-name --show-trace
# Print specific option valuenix eval .#nixosConfigurations.target-name.config.ghaf.myOptionComplete Example
Section titled “Complete Example”Here’s a complete module example implementing a new service:
# SPDX-FileCopyrightText: 2022-2026 TII (SSRC) and the Ghaf contributors## Example monitoring service module for Ghaf#{ config, lib, pkgs, ... }:let cfg = config.ghaf.services.monitoring;in{ _file = ./monitoring.nix;
options.ghaf.services.monitoring = { enable = lib.mkEnableOption "system monitoring service";
port = lib.mkOption { type = lib.types.port; default = 9100; description = "Port for the monitoring endpoint"; };
interval = lib.mkOption { type = lib.types.int; default = 15; description = "Collection interval in seconds"; example = 30; };
extraFlags = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; description = "Additional flags to pass to the monitoring daemon"; example = [ "--verbose" "--debug" ]; }; };
config = lib.mkIf cfg.enable { # Add monitoring package environment.systemPackages = [ pkgs.prometheus-node-exporter ];
# Configure systemd service systemd.services.ghaf-monitoring = { description = "Ghaf System Monitoring"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ];
serviceConfig = { Type = "simple"; ExecStart = lib.concatStringsSep " " ([ "${pkgs.prometheus-node-exporter}/bin/node_exporter" "--web.listen-address=:${toString cfg.port}" ] ++ cfg.extraFlags); Restart = "on-failure"; RestartSec = 5; }; };
# Open firewall port networking.firewall.allowedTCPPorts = [ cfg.port ];
# Add to journald for log forwarding services.journald.extraConfig = '' SystemMaxUse=100M ''; };}See Also
Section titled “See Also”- Module Conventions - Detailed conventions
- Anti-Patterns - What to avoid
- Adding Features - Feature-specific modules
- Creating VMs - VM base modules