Modules as a consumer
Modules as a consumer
Goal: Use the NixOS module system as a consumer—read options and source for real modules, apply mkIf / mkDefault concepts correctly, and change behavior via options rather than forking nixpkgs.
Why this day exists
Copy-pasting entire module files into your repo is a maintenance trap. Power users set options, import small local modules, and only override when necessary. That skill starts with reading.
Theory 1 — A module is a function (usually)
{ config, lib, pkgs, ... }: {
options = { /* declarations */ };
config = { /* implementations */ };
}| Argument | Role |
|---|---|
config |
Final merged configuration (lazy) |
lib |
Library + mkOption, mkIf, … |
pkgs |
Package set for this system |
… |
Other module args |
Some files are plain attrsets; most nixpkgs modules use the function form.
Theory 2 — Options vs config
| Side | Meaning |
|---|---|
| options | Schema: what may be set, types, defaults, descriptions |
| config | Values and implementations that produce system artifacts |
As a consumer you mostly set option values:
{
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
}You are writing config fragments that merge into the global config.
Theory 3 — Merge semantics (intuition)
Multiple modules set the same option; the module system merges by type:
| Type vibe | Merge idea |
|---|---|
| Boolean | Priority rules / any true patterns depend on option |
| List | Concatenate |
| Attrset | Deep merge of attributes |
| Exclusive values | Conflict error or priority |
This is why shallow // (the language attrsets chapter) is not how NixOS config works.
Theory 4 — lib.mkIf
{ config, lib, ... }: {
config = lib.mkIf config.services.foo.enable {
environment.systemPackages = [ /* … */ ];
networking.firewall.allowedTCPPorts = [ 8080 ];
};
}mkIf cond value includes value only when cond is true—without breaking merge when false (unlike a raw if that might remove structure incorrectly in some patterns).
Consumer pattern: you rarely write mkIf until the custom modules chapter; you benefit from modules that use it when you set enable = true.
Theory 5 — lib.mkDefault and priority
services.openssh.enable = lib.mkDefault true;| Helper | Idea |
|---|---|
mkDefault |
Low priority default; easy to override |
mkForce |
High priority; stomps others |
mkOverride n |
Numeric priority control |
Consumer rule: prefer ordinary assignment first. Use mkForce only when you understand why a default wins.
# last resort
networking.hostName = lib.mkForce "lab";Theory 6 — How to read a nixpkgs module
Step path
- Find option on search.nixos.org/options
- Note default and type
- Open “Declared in” source file
- Skim
optionsthenconfig = mkIf cfg.enable
Local source on a NixOS system
# approximate — paths vary by version
nix eval --raw 'nixpkgs#path'
# then browse nixos/modules/...Or:
nix repl
:lf nixpkgs
# explore; or use ripgrep in a checkoutWhat to extract mentally
| Question | Why |
|---|---|
What does enable turn on? |
Side effects |
| Which ports open? | Firewall |
| Which user created? | Permissions |
| What state dir? | Backup/persist |
| Hardening defaults? | Security |
Theory 7 — Consumer anti-patterns
| Anti-pattern | Prefer |
|---|---|
| Vendor-copy whole module | Set options; small local module |
| Patch store files | Declarative options / overlays later |
mkForce everything |
Understand merge |
| Ignore option descriptions | Read them |
Edit generated unit files in /etc |
Change Nix; rebuild |
Theory 8 — imports as composition
{
imports = [
./hardware-configuration.nix
./modules/ssh.nix
./modules/packages.nix
];
}Each import is a module. Order rarely matters for merges (priorities do). the custom modules chapter authors one.
Worked examples bank
Example A — Read openssh options
Conceptually you will find:
options.services.openssh.enable = mkEnableOption "…";
config = mkIf cfg.enable { … };Set as consumer:
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;Example B — Override a default carefully
# module might mkDefault some package set
# you add:
environment.systemPackages = [ pkgs.htop ];
# lists merge — usually no mkForce neededExample C — When mkForce appears in blogs
Often silencing a conflict:
boot.kernelPackages = lib.mkForce pkgs.linuxPackages_latest;Use sparingly; document why.
Lab 1 — Study three modules
Pick three from:
services.openssh
services.timesyncdortime
programs.git
networking.firewall
services.nginx(even if unused)
For each, fill:
| Module | enable option | 2 interesting options | One side effect |
|---|---|---|---|
Keep notes in ~/nixos-config/docs/modules-study.md.
Lab 2 — Change behavior via options only
Without forking module source:
- Tighten or loosen one SSH setting
- Change timezone or NTP-related option
- Adjust firewall ports
Rebuild; verify with systemctl / ss / ssh.
Lab 3 — Trace an option to source
Using search.nixos.org or local nixpkgs:
- Choose
networking.firewall.enable
- Find declaring file
- Read how firewall backend is selected
Write five lines on what enable = true actually does.
Lab 4 — Priority experiment (safe)
In a throwaway module snippet:
{ lib, ... }: {
networking.hostName = lib.mkDefault "default-name";
}And in main config:
networking.hostName = "lab";Confirm final hostname is lab. Then reverse priorities with mkForce experimentally and reset to clean state.
Lab 5 — Consumer checklist
Before inventing a custom module tomorrow, ask:
- Does an option already exist?
- Can two options combine?
- Am I fighting a default that
mkDefaultset?
Common gotchas
| Symptom | Theory |
|---|---|
| Option does not exist | Typos / wrong release |
| Infinite recursion | config referring badly in custom modules |
| Silent override | Priorities / mkForce elsewhere |
| Copied outdated module | Diverges from nixpkgs |
mkIf confusion |
Study enable patterns first |
Checkpoint
- Three modules studied with notes
- Behavior changed via options only
- Can explain options vs config
- Can explain mkIf / mkDefault roles
- Avoided forking nixpkgs modules
Commit
cd ~/nixos-config
git add .
git commit -m "day18: modules as consumer mkIf mkDefault literacy"