a dotfile but it's really big
Nix Best Practices#
Code Style#
- Use
nixfmtfor formatting (set as formatter in flake, runnix fmtbefore committing) - Follow the Nixpkgs contributing guide for style conventions
- Use descriptive variable names that explain purpose
- Prefer
let ... inblocks for local bindings over deep nesting - Keep derivations and functions small and composable
- Use
lib.mkIf,lib.mkWhen,lib.optionalsfor conditional logic - Use
lib.getExeandlib.getExe'for accessing package executables
Error Handling#
# GOOD: Proper error handling with builtins.tryEval
let
configPath = ./config.json;
config = builtins.tryEval (builtins.fromJSON (builtins.readFile configPath));
in
if config.success then config.value else throw "Failed to parse config"
# GOOD: Using assert statements for validation
assert lib.assertMsg (cfg.port > 0 && cfg.port < 65536) "Port must be between 1 and 65535";
cfg
# GOOD: Using lib.mapAttrs' with lib.nameValuePair for building attribute sets
lib.mapAttrs' (argName: argValue:
lib.nameValuePair "${argName}+${dir.name}" {
action = {
"${argValue}-${dir.value}" = [ ];
};
}
) act
# BAD: Silent failures or incomplete error messages
let
data = builtins.readFile configPath;
config = builtins.fromJSON data; # Will throw cryptic error if invalid
in config
Nix Module System#
- Use
lib.mkOptionType,lib.mkDefault,lib.mkForceappropriately - Prefer
mkOptionwith propertype,default, anddescriptionfields - Use
mkIffor conditional module options rather than deep nesting - Keep modules focused - one module per responsibility
- Use
importsto compose modules rather than duplicating code - Use
lib.getExe pkgs.packageNamefor executable paths in configuration
# GOOD: Well-structured module option
{ lib, config, ... }:
{
options.my.username = lib.mkOption {
type = lib.types.str;
description = "The username for the current user.";
default = "kar";
};
config = lib.mkIf (config.my.username != "root") {
users.users.${config.my.username} = {
home = "/home/${config.my.username}";
initialPassword = "";
isNormalUser = true;
extraGroups = [
"networkmanager"
"docker"
"wheel"
];
};
};
}
Flake Patterns#
- Use
flake-partsfor modular flake structure - Use
withSystemfor system-specific configuration - Provide
devShells,packages,checks,formatterper system - Lock
nixpkgsreference using channel URLs or specific revisions - Provide formatter and linter in
devShells - Use
nixConfigfor Nix configuration (experimental features, substituters)
System Management#
- Use
easy-hostsfor organizing hosts by class (desktop, server, wsl) and tags - Define shared modules for all systems in
config.easy-hosts.shared.modules - Use perClass and perTag for class-specific and tag-specific modules
- Use
mkSystem'for creating individual systems with proper specialArgs
Overlay Patterns#
- Use
overrideAttrsfor patching existing packages - Use
fetchurlorfetchFromGitHubfor patches with hash verification - Use
callPackageto add packages to overlay - Keep overlays focused and well-documented
Package Building#
- Use
pkgs.buildGoModulefor Go packages with proper vendorHash - Use
pkgs.fetchFromGitHubfor GitHub sources with hash verification - Set
env.CGO_ENABLED,flags, andldflagsappropriately for Go - Include proper
metawith description, homepage, license, maintainers - Use
trimpathand static linking flags for production binaries
Security#
- Never commit secrets or API keys in Nix files
- Use
sops-nixfor secrets management in production - Use sandbox mode in
nix.conf(sandbox = true) for builds - Review network access in derivations - use
allowedRequisitesfor purity - Use
fetchFromGitHuborfetchurlwith hash verification
Dependency Management#
- Use specific Git revisions/commits for Git dependencies (not branches)
- Always include
sha256hash for fetch functions - Use
inputs.nixpkgs.followsto avoid duplicating inputs - Pin
nixpkgsin production configurations usingflake.lock - Prefer
flake-inputsoverbuiltins.fetchTarballfor external sources - Use
callPackagepattern for package dependencies
Testing#
- Use
nix-instantiate --parseto check for syntax errors - Use
nix-buildwith-Ato test specific attributes - Write checks in
perSystem.checksfor package testing - Use
nix flake checkfor flake validation - Test modules with
nixos-rebuild dry-buildornixos-rebuild test
Evaluation#
- Use
lib.warnIfandlib.deprecatedfor deprecation notices - Avoid
builtins.tracein production code (use for debugging only) - Use
lib.lists.foldl'orlib.lists.foldl'for strict left folds - Prefer
lib.mapAttrsandlib.mapAttrs'overbuiltins.mapfor attribute sets - Use
lib.filterAttrsfor filtering attributes - Use
lib.mergeAttrsListfor merging lists of attribute sets - Use
lib.attrsToListfor converting attributes to list of name-value pairs
Performance#
- Use
lib.optionalsandlib.concatMapfor list operations - Avoid deep recursion - use
foldl'for accumulation - Use
overrideAttrsfor modifying packages instead of rewriting - Prefer
stdenv.mkDerivationover rawderivationfor packages - Use
pkgs.callPackagewith explicit arguments for clarity
Home-Manager Module System#
Home-manager modules manage user-level configuration (home directory, user packages, programs).
Module Structure#
modules/
dev/
home.nix # Home-manager module with osConfig parameter
nixos.nix # NixOS module importing home.nix via homeModules
shell/
default.nix # Imports sub-modules
nushell.nix # Actual configuration
Home-Manager Module Pattern#
# modules/dev/shell/default.nix
{
imports = [
./atuin.nix
./nushell.nix
./starship.nix
./zellij.nix
];
}
Home-Manager with osConfig#
# modules/dev/home.nix
{
osConfig ? { },
lib,
...
}:
let
inherit (lib) mkEnableOption;
in
{
# Inherit options from NixOS module configuration
config.dev = {
inherit (osConfig.dev or { })
shell
editor
vcs
tools
;
};
# Define options (mirrors NixOS module)
options.dev = {
enable = mkEnableOption "all development tools";
shell.enable = mkEnableOption "shell-related tools";
editor.enable = mkEnableOption "editor tools";
vcs.enable = mkEnableOption "version control tools";
tools.enable = mkEnableOption "development utilities";
};
# Import sub-modules
imports = [
./shell
./editor
./vcs
./tools
];
}
Flake Integration#
# flake.nix modules section
{
imports = [ ../systems ];
perSystem = { ... }: {
# ...
};
flake = {
homeModules = {
dev = import ./dev/home.nix;
desktop = import ./desktop/home.nix;
};
nixosModules = {
dev = import ./dev/nixos.nix;
desktop = import ./desktop/nixos.nix;
};
};
}
Usage in System Configuration#
# systems/kiwi.nix (NixOS)
{
imports = [
inputs.self.nixosModules.dev
# ... other modules
];
dev.enable = true;
dev.shell.enable = true;
}
# modules/desktop/home.nix (Home-Manager)
{ config, self, ... }:
{
imports = [
self.homeModules.dev
# ... other home modules
];
dev.enable = true;
}
Key Differences from NixOS Modules#
| Aspect | NixOS Module | Home-Manager Module |
|---|---|---|
| Special args | { config, lib, pkgs, ... } |
{ config, lib, pkgs, osConfig ? {}, ... } |
| System config | Direct access to config.* |
Inherits via osConfig.dev |
| User packages | home.packages |
home.packages |
| Programs | programs.* |
programs.* |
| System services | services.* |
Not available |
XDG Directories#
Home-manager manages XDG directories via xdg.configFile, xdg.dataFile, etc.:
{ config, ... }:
{
xdg.configFile."opencode/agents" = {
source = ./agents;
recursive = true;
};
programs.opencode = {
enable = true;
package = opencodePkg;
settings = {
theme = "catppuccin-macchiato";
};
};
}