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 .nix files 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-dir flags added, or icon paths patched.
  • Why: By using explicit wrapProgram calls in the module itself (the “clean-room” approach), we avoid brittle .desktop file sed-patching and Nix store permission errors that plagued older symlinkJoin abstractions.

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.nix and user profiles (e.g. alienzj.nix) can set identity alongside system-user fields without polluting the users.users submodule.

🧬 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

ValueSource
Host namemapAttrs key from hosts/<name>/networking.hostName
User identitymodules/user.nix option declarations, set per-user in profile
Thememodules.theme.active set in host configuration.nix
Dotfiles pathtoString 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.