Modules
Updated: September 28, 2024
Modules are a way to organize and share code.
They can be used for creating different configurations, without having to write the same code all over again.
This also makes the code more maintainable, as a change or update to a module will be reflected everywhere.
Let’s dive into the key features, and how to use them.
Everything from darwin down lets consider a module file/dir.
Example of dir tree one may layout:
flake.nix
├── content/
├── hosts/
│ ├── bumblebee/
│ │ ├── configuration.nix
│ │ ├── hardware.nix
│ │ ├── home.nix
│ ├── ratchet/
│ │ ├── configuration.nix
│ │ ├── hardware.nix
│ │ ├── home.nix
├── darwin/...
│ ├── default.nix
│ ├── cli.nix/...
│ ├── apps/...
├── nixos/...
│ ├── default.nix
│ ├── cli.nix/...
│ ├── apps/...
├── homeManager/
│ ├── default.nix/
│ ├── cli/
│ │ ├── nvim.nix
│ │ ├── fh.nix
│ │ ├── zsh.nix
│ ├── apps/
│ │ ├── brave.nix
│ │ ├── discord.nix
│ │ ├── tailscale.nix
Modules could be used directly in configuration.nix using imports but it is not recommended like so:
{pkgs, lib, ...}: {
imports = [
./hardware.nix
./module1
./module2
./module3
];
}
Instead we can create enable options to control the modules.
In order to do that we need to use lib to provide functions
and config to access value we place inside it.
Let’s look at an example module, tailscale.nix:
{ pkgs, lib, config, ... }:
# The options section creates an enable option
options = {
tailscale.enable = lib.mkEnableOption {
"enables tailscale";
};
};
# This line creates a section of values to be enabled
config = lib.mkIf config.tailscale.enable {
# Configure Tailscale
programs.tailscale = {
enable = true;
};
services.tailscale = {
enable = true;
openFirewall = true;
};
boot.kernel.sysctl = {
# for tailscale exit node
"net.ipv6.conf.all.forwarding" = "1";
};
}
From here we can create a single file bundled with all the modules to be imported
and then enable the ones desired for a particular build in its configuration.nix:
{ pkgs, lib,... }: {
imports = [
./apps/appbundle.nix
];
tailscale.enable = true;
}
Where appbundle.nix:
{ pkgs, ... }: {
imports = [
./brave.nix
./discord.nix
./tailscale.nix
];
}
We can now also create conditional logic to other modules:
{ pkgs, lib,... }: {
imports = [
./apps/appbundle.nix
];
# Enable tailscale only if discord is enabled
tailscale.enable = lib.mkIf config.discord.enable true;
}
Now lets say we want some modules to be enabled by default.
This is useful for modules that are used on all systems or
to enable an entire category of modules but leave a few disabled
that not all systems would use in lets say our clibundle.nix:
{ pkgs, ... }: {
imports = [
./nvim.nix
./ohmyzsh.nix
./zsh.nix
];
nvim.enable = lib.mkDefault true;
zsh.enable = lib.mkDefault true;
}
Here by importing clibundle.nix nvim and zsh will be enabled but not ohmyzsh.
They can then be disabled by setting them to false in configuration.nix. We can use other lib functions to determine priority and enable/disable modules.
# higher number has less priority
# These are the default values
lib.mkOptionDefault = mkOverride 1500;
lib.mkDefault = mkOverride 1000;
lib.mkImageMediaOverride = mkOverride 60;
lib.mkForce = mkOverride 50;
lib.mkVMOverride = mkOverride 10;
As a flake.nix we can create outputs for each build:
description = "NixOS";
# ...
outputs = { nixpkgs, ... }@inputs: {
nixosConfigurations.default = nixpkgs.lib.nixosSystem {
specialArgs = {inherit inputs; };
modules = [
./hosts/bumblebee/configuration.nix
./nixos
];
};
# ... more configurations like above
}
By using modules in a tree structure we can easily build out configurations
for mac, windows, nixos, VMs, pi’s, etc. Home Manager modules are best applied, well, as a module. example home.nix:
{ inputs, ... }: {
home-manager."username" = {
extraSpecialArgs = { inherit inputs; };
users = {
modules = [
./home.nix
inputs.self.outputs.homeManagerModules.default
];
};
};
}