Nix Expressions
Nix Expressions & Design Patterns
A deep dive into the custom Nix functions and architectural patterns that make this repository highly modular and declarative.
🛠️ Core Library Functions (lib/)
Our custom library simplifies the standard NixOS module system, making it more concise and easier to manage.
1. Unified Option Definition (mkOpt, mkBoolOpt)
Instead of long mkOption blocks, we use:
mkOpt types.str "default": Defines a string option with a default.mkBoolOpt true: Defines a boolean option.mkOpt' types.lines "" "desc": Defines an option with a custom description.
2. Auto-loading Pipeline (mapModules)
We use recursive folder scanning to import Nix files automatically. This eliminates the need for manual imports = [ ./file.nix ... ] lists.
mapModules ./dir import: Imports all.nixfiles in a directory.mapModulesRec: Scans subdirectories as well.
🏗️ Functional Wrappers & Pipelines
To improve software organization and security, we implement custom “pipeline” functions.
The Jailing Wrapper (wrapFakeHome & mkWrapper)
Located in lib/pkgs.nix, these functions create a secure execution environment for apps that refuse to follow XDG standards by redirecting $HOME to a “fake” directory (usually ~/.local/user).
1. wrapFakeHome pkg binName
Creates a minimal, reliable wrapper for a straightforward application, targeting its exact binary.
- Best for: Simple GUI apps (Zoom, QQ) or CLI tools.
- Implementation: It is a thin helper around
mkWrapper.
2. mkWrapper pkg postBuild
The raw wrapping primitive used when complex logic is required.
- Best for: Complex GUI applications (Steam, JetBrains IDEs, VSCode) that need multiple binaries wrapped, specific
--user-data-dirflags added, or icon paths patched. - Why: By using explicit
wrapProgramcalls in the module itself (the “clean-room” approach), we avoid brittle.desktopfile sed-patching and Nix store permission errors that plagued oldersymlinkJoinabstractions.
Desktop Entries (mkLauncherEntry & mkFakeHomeEntry)
mkLauncherEntry
Generates an imperatively created .desktop application entry. Prefixes the name with launcher..
Signature: title: { prefix ?, name, description ?, icon, exec, categories ? [] } -> derivation
mkFakeHomeEntry
Generates an isolated .desktop application entry for an app that needs to run in the Fake Home but doesn’t have a native Nix package (or requires specific launch flags like --private-window).
Signature: title: { name, description ?, icon, exec, categories ? [] } -> derivation
Key Behavior for XDG MIME Bindings:
This function wraps mkLauncherEntry and creates a .desktop file named launcher.<name>.desktop. It also automatically generates a shell script wrapper for the Exec line so that $HOME is correctly set without complex escaping. If you need to set this app as a default handler in modules.xdg.mime.defaultApplications, you must use the launcher. prefix.
# Creating the isolated entry
(mkFakeHomeEntry "Librewolf (Private)" {
name = "librewolf-private";
exec = "librewolf --private-window %U";
icon = "librewolf";
})
# Binding it in XDG
modules.xdg.mime.defaultApplications = {
"x-scheme-handler/http" = ["launcher.librewolf-private.desktop"];
};
The Module Bridge (mkAliasDefinitions)
We use mkAliasDefinitions to bridge related option namespaces, keeping module config concise.
home.* → home-manager.users.<name>.* (home.nix):
home.file, home.configFile, home.dataFile are aliased to their long-form Home Manager equivalents.
- Benefit: write
home.configFile."app/config".source = ...in any NixOS module.
config.user.* → users.users.<name>.* (default.nix):
A subset of config.user attributes (name, description, home, group, uid,
extraGroups, isNormalUser) are forwarded to users.users.<name> via inherit.
Other config.user fields (email, github, signing, ssh keys, etc.) live
at config.user.* only — they are NOT forwarded.
- Benefit:
user.nixand user profiles (e.g.alienzj.nix) can set identity alongside system-user fields without polluting theusers.userssubmodule.
🧬 Architectural Patterns
The “Decoration” Pattern
Used in our theme system, this pattern ensures that visual changes are non-destructive. A decoration (like a CSS theme) is only applied if the application itself is already enabled.
config = mkIf (config.modules.desktop.browsers.librewolf.enable) {
# ... themes apply here ...
}
Declarative Provisioning (ensures)
Used in the PostgreSQL module, this pattern handles the entire database lifecycle (user creation, DB creation, password rotation) at boot time via Systemd post-start hooks. This removes the need for manual SQL initialization scripts.
🧬 Pure Hermetic Evaluation
The flake is fully hermetic — zero --impure flags, zero getEnv calls,
zero HEYENV. Build-time paths use toString self (the flake store path),
which contains all config files, modules, and themes.
Host / User / Theme Resolution
| Value | Source |
|---|---|
| Host name | mapAttrs key from hosts/<name>/ → networking.hostName |
| User identity | modules/user.nix option declarations, set per-user in profile |
| Theme | modules.theme.active set in host configuration.nix |
| Dotfiles path | toString self at build time; $DOTFILES_HOME at runtime (set by shell init) |
Runtime DOTFILES_HOME
DOTFILES_HOME is set by /etc/zshenv (generated by modules/hey.nix) and
points to the flake store path. It is a runtime convenience for Janet CLI tools
and shell scripts — not a build-time input. No --impure needed.
For more context, see Toolchain & System Integration.