An Overview

This documentation offers a comprehensive walkthrough of NixOS for beginners, drawing directly from the insights and practical examples presented in the two provided YouTube videos. We’ll explore NixOS’s configuration-driven philosophy, dive into module creation and flake management, and uncover the power of Home Manager for taming your dotfiles.


Embracing the NixOS Philosophy: Configuration as Code

NixOS diverges from traditional Linux distributions by embracing configuration as code. Instead of using package## Notes: managers like APT or graphical interfaces, you define your entire system—applications, services, configurations—within declarative configuration files written in the Nix Expression Language.

Advantages of the NixOS Approach

  1. Reproducibility: Your entire system is codified, making it easily replicable across machines. Just copy the configuration files for consistent setups across deployments.
  2. Modularity: Break down your configuration into reusable components called modules, each responsible for a specific aspect like Emacs, a web server, or a window manager. This enhances organization and simplifies complex setups.
  3. Rollback and Disaster Recovery: NixOS’s configuration-centric nature simplifies disaster recovery. Reinstall and rebuild your system from your configuration files, restoring it to a known state. Rollback functionality reverts to previous configurations if updates cause issues. A user in the sources recounted an experience where their wife’s NixOS system encountered a problem with full-disk encryption after an update, making the passphrase inoperable. Using NixOS, they reinstalled the system and restored her custom setup in a short time, showcasing the power of this approach for real-world issues.

Demystifying NixOS Configuration

The heart of NixOS lies in the configuration file, usually at /etc/nixos/configuration.nix. This file, written in the Nix Expression Language, might appear daunting initially, but understanding its key elements is essential.

Core Components of NixOS Configuration

  1. Packages: Specify the packages to install within your configuration.nix. Packages are the fundamental building blocks of your system—utilities like vim and full-fledged applications. Note that package names in NixOS can be unintuitive (gnome.geary for the Geary email client). Resources like myni.com can assist in finding the correct names.
  2. Modules: Modules group related configurations, improving organization and maintainability. For example, separate modules can manage your shell configuration, window manager settings, or web server setup. You can then import these modules into your configuration.nix to activate their configurations.
  3. Variables: NixOS lets you define variables to store values used throughout your configuration. This promotes consistency and eases system-wide modifications. For example, changing a variable storing your username will update it across your configuration.

Unpacking the Nix Store

NixOS employs a unique approach to package storage—the Nix store. This central, immutable location houses all software components and their dependencies. Each package receives a unique path within the store, ensuring isolation and preventing dependency conflicts. When you “install” a package, NixOS retrieves it (or builds it if necessary) and places it in the Nix store, linking it to the appropriate location in your system’s file system. This approach ensures a consistent and reproducible system state, as package installations do not modify existing files in shared locations.


Modules: Achieving Configuration Zen

Modules are key to maintaining a well-organized and manageable NixOS system, especially as your configuration grows. They encapsulate specific configuration aspects into separate files, preventing your main configuration.nix from becoming unwieldy.

Creating Your First NixOS Module

  1. Define a Function: A NixOS module is simply a function that takes an attribute set (containing at least config and packages) as input and returns an attribute set of configuration options.
  2. Name Your Module (File): Save your module function in a .nix file. While you can technically name it anything, it’s good practice to use a descriptive name that reflects its purpose (e.g., shell.nix, emacs.nix, webserver.nix). Note, however, that some file names have special significance, like shell.nix, so slight variations are recommended in such cases (sh.nix for shell configuration).
  3. Import Your Module: Include your module in your main configuration.nix using the imports list. Simply add the path to your module file within this list, and NixOS will load and apply its configurations when building your system.

Real-World Module Example: Organizing Shell Configuration

Let’s illustrate this with a practical example from the sources, creating a module (sh.nix) to manage your Bash and Zsh configuration:

# sh.nix
{ config, packages, ... }:

let
  myAliases = {
    "ll" = "ls -l";
    "la" = "ls -la";
  };

in
{
  programs.bash = {
    enable = true;
    shellAliases = myAliases;
  };

  programs.zsh = {
    enable = true;
    shellAliases = myAliases;
  };
}

Explanation:

  1. Module Function: We define our module as a function that accepts config, packages, and any other potential inputs (represented by ...).
  2. Variable for Reusability: The myAliases variable stores our shell aliases. This promotes consistency and avoids repetition.
  3. Configuring Bash and Zsh: We configure both Bash and Zsh within the module. The enable = true; ensures that our configurations are applied, and shellAliases = myAliases; uses the aliases we defined earlier.

Importing the Module:

In your configuration.nix, add the sh.nix module to the imports list:

{
  imports = [
    ./hardware-configuration.nix
    ./sh.nix # Our new shell configuration module
  ];

  # Other configuration options...
}

Now, when you rebuild your NixOS system, the shell configurations within sh.nix will be applied. This modular approach makes your configuration cleaner, more organized, and easier to manage, especially as your system’s complexity grows.


Taming Dotfiles with Home Manager

Home Manager extends the NixOS philosophy to your user environment and dotfiles, providing a declarative and reproducible way to manage them.

Benefits of Home Manager

  1. Consistent Dotfiles Across Machines: Define your dotfile configurations once and apply them to different systems. No more manually copying and syncing configuration files.
  2. Rollback and Experimentation: Switch between different dotfile configurations or roll back to previous states effortlessly, just like with your system configuration. This is particularly helpful when experimenting with new setups or migrating between window managers, as highlighted by a user in the sources who used Home Manager to transition from XMonad to Hyprland smoothly.
  3. Clean Separation of User and System Configuration: Home Manager can be installed as a standalone tool, keeping your user-specific configurations separate from your system-wide settings in configuration.nix.

Getting Started with Home Manager

  1. Installation: Home Manager can be installed in two ways: standalone (managed by the user) or as a NixOS module (requiring root privileges). The standalone approach offers a cleaner separation for single-user systems, while the module approach is better for managing multiple user accounts across a system.
  2. Configuration File (home.nix): Home Manager uses a home.nix file to define your user environment and dotfile configurations. This file mirrors the structure and principles of configuration.nix, allowing you to leverage your existing NixOS knowledge.
  3. Activating Home Manager Configuration: Use the home-manager switch command to apply the configurations defined in your home.nix file.

Home Manager Example: Customizing Bash Aliases

Let’s enhance our previous shell configuration example using Home Manager:

# home.nix
{ config, pkgs, ... }:
{
  programs.bash = {
    enable = true;
    shellAliases = {
      "ll" = "ls -l";
      "la" = "ls -la";
    };
  };
}

Explanation:

  1. home.nix: This configuration resides in your home.nix file, indicating its management by Home Manager.
  2. Familiar Structure: The structure is identical to our previous module example, highlighting the consistency between NixOS and Home Manager configuration.

By running home-manager switch, Home Manager will generate the necessary files and symbolic links to make these aliases available in your Bash shell.


Leveling Up with Flakes: Reproducibility and Version Management

While channels offer a way to manage package versions in NixOS, the community has largely transitioned to flakes. Flakes provide enhanced reproducibility and streamlined dependency management.

Advantages of Using Flakes

  1. Precise Version Locking: Flakes use a flake.lock file to record the exact versions of all packages in your system, ensuring that you can recreate the same environment across machines or at different points in time.
  2. Git Integration: You can track your flake configuration, including flake.lock, using Git, providing version control and a history of your system configuration.

Integrating Home Manager with Flakes

To incorporate Home Manager into your flake-based configuration, you need to make a few adjustments to your flake.nix file:

  1. Home Manager Input: Add Home Manager as an input to your flake, specifying its source and branch (stable or unstable). Ensure that Home Manager and your Nix package repository (nixpkgs) use the same version of Nix packages to avoid conflicts.
  2. Home Configurations Output: Similar to how you define your NixOS system configuration as an output, create a section for your Home Manager configurations. The structure of this section mirrors the NixOS configuration, specifying your username, the architecture, and the home.nix file as the module.

Example flake.nix with Home Manager Integration

{
  description = "My NixOS and Home Manager Configuration";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; # Adjust branch if needed
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

 outputs = { self, nixpkgs, home-manager, ... }:
   let
     system = "x86_64-linux";
     pkgs = import nixpkgs { config = {}; system = system; };
   in
   {
     nixosConfigurations.myNixOS = nixpkgs.lib.nixosSystem {
       system = system;
       modules = [
         # Your NixOS modules
       ];
     };

     homeConfigurations.librephoenix = home-manager.lib.homeManagerConfiguration {
       pkgs = pkgs;
       modules = [
         ./home.nix # Path to your home.nix file
       ];
     };
   };
}

Explanation:

  1. Inputs: We define both nixpkgs and home-manager as inputs to our flake. We also ensure that they both use the same version of Nix packages.
  2. Outputs: We define two outputs: nixosConfigurations.myNixOS for our system configuration and homeConfigurations.librephoenix for our Home Manager configuration. We use a let binding to define variables for our system architecture (system) and the Nix package set (pkgs), making our code cleaner and more maintainable.
  3. NixOS System Configuration: We use the nixpkgs.lib.nixosSystem function to define our system configuration, specifying the system architecture and the modules to include.
  4. Home Manager Configuration: We use the home-manager.lib.homeManagerConfiguration function to define our Home Manager configuration, passing in the pkgs set and specifying our home.nix file as the module.

Managing Your Flake

  • Updating Packages: Use nix flake update to fetch the latest package versions from your defined inputs and update the flake.lock file.
  • Applying System Changes: Run sudo nixos-rebuild switch --flake . to rebuild your system using the updated flake.lock file, incorporating any package updates or configuration changes.
  • Activating Home Manager: Execute home-manager switch -flake . to apply the latest dotfile configurations from your home.nix file based on your updated flake.lock.

Additional Tips and Considerations

  • Command Line Proficiency: NixOS relies heavily on the command line, so familiarity with terminal commands and tools is beneficial.
  • Experiment in a VM: For beginners, starting with NixOS in a virtual machine is highly recommended. This provides a safe space to explore configurations without jeopardizing your primary system.
  • Resource Recommendations: Allocate sufficient resources to your VM, including ample memory, CPU cores (especially for compilation), and storage space (at least 100 GB) for a smooth experience.

Conclusion

By embracing the principles of configuration as code, modules, Home Manager, and flakes, you can unlock the full potential of NixOS, creating a system tailored to your needs, reproducible across machines, and resilient to change. The insights and practical examples from the sources, coupled with the comprehensive explanations provided in this documentation, will empower you to embark on your NixOS journey with confidence.