How to Install a One-Off Package in NixOS

As NixOS enthusiasts, we often find ourselves in situations where we need a specific package version that’s not readily available in our current stable channel. Perhaps a particular application requires a newer release, or we want to experiment with a cutting-edge version before it’s officially integrated. Staying on a stable channel like nixos-16.03 provides reliability, but it can sometimes lag behind the latest package offerings. In this comprehensive guide, we’ll explore various methods to install one-off packages in NixOS without compromising the stability of your system or hindering future upgrades. We aim to provide a detailed, practical approach to address this common challenge.

Understanding the NixOS Package Management Paradigm

Before diving into the solutions, it’s essential to grasp the fundamental principles behind NixOS’s package management. NixOS uses a purely functional approach, meaning that every package is built from its source code with all dependencies declared explicitly. This results in reproducible builds and eliminates dependency conflicts. The Nix package manager stores packages in the Nix store (/nix/store), identified by content-addressed hashes. This unique storage mechanism allows multiple versions of the same package to coexist peacefully on the system.

The Role of Channels

NixOS channels are essentially pointers to specific snapshots of the Nix Packages collection (nixpkgs). Each channel represents a set of package versions that have been tested and deemed stable for a particular release. Staying on a stable channel ensures that your system receives consistent updates and avoids introducing potentially unstable software. However, this stability comes at the cost of immediate access to the newest package releases.

Addressing the One-Off Package Installation Challenge

The core challenge lies in installing a package version newer than what’s available in our stable channel (e.g., nixos-16.03) without disrupting the overall system stability or hindering future upgrades when the stable channel catches up. Let’s explore several strategies to tackle this problem.

Method 1: Overriding Package Attributes in configuration.nix

As suggested in the IRC conversation, overriding the src and version attributes within your configuration.nix file is a viable option for packages that already exist in your current channel. This approach leverages Nix’s attribute overriding mechanism to customize the package build process.

Step-by-Step Implementation

  1. Locate the Package Definition: Identify the existing Nix expression for the package you want to override. This can typically be found in the nixpkgs repository on GitHub or by inspecting the output of nix-env -qaP \*packagename\*.

  2. Override src and version: Add an overlay to your configuration.nix file to override the src and version attributes of the package. Here’s an example using nodejs-6_x as the target package:

    { config, pkgs, ... }:
    
    {
      nixpkgs.overlays = [
        (self: super: {
          nodejs-6_x = super.nodejs-6_x.overrideAttrs (oldAttrs: {
            version = "6.22.12"; # Replace with the desired version
            src = pkgs.fetchurl {
              url = "https://nodejs.org/dist/v6.22.12/node-v6.22.12.tar.gz"; # Replace with the appropriate URL for the desired version
              sha256 = "000000000000000000000000000000000000000000000000000000000000"; # Replace with the correct SHA256 hash
            };
          });
        })
      ];
    }
    
    • nixpkgs.overlays: This setting allows you to modify the package set defined by nixpkgs. Overlays are functions that take the original nixpkgs set and return a modified version.
    • (self: super: { ... }): This is a lambda function that receives self (the modified nixpkgs) and super (the original nixpkgs).
    • nodejs-6_x = super.nodejs-6_x.overrideAttrs (oldAttrs: { ... }): This line accesses the original nodejs-6_x package from super and uses overrideAttrs to modify its attributes. The oldAttrs argument provides access to the original attributes of the package.
    • version = "6.22.12": This sets the desired version number.
    • src = pkgs.fetchurl { ... }: This specifies the source code for the new version.
      • url: The URL of the source code archive.
      • sha256: The SHA256 hash of the source code archive. This is crucial for ensuring the integrity of the downloaded source code. You can obtain this hash using nix-hash --type sha256 --flat <url>.
  3. Calculate the SHA256 Hash: Use the nix-hash command to compute the SHA256 hash of the source code archive:

    nix-hash --type sha256 --flat https://nodejs.org/dist/v6.22.12/node-v6.22.12.tar.gz
    

    Replace the URL with the actual URL of the package source. Paste the output of this command into the sha256 attribute in your configuration.nix file.

  4. Apply the Configuration: Rebuild your NixOS configuration to apply the changes:

    sudo nixos-rebuild switch
    

Advantages

  • Simple and straightforward for minor version updates.
  • Keeps the package definition within your configuration.nix for easy management.

Disadvantages

  • Requires manual intervention to update the src and version attributes when a new version is released.
  • If the package definition undergoes significant changes in nixpkgs, your overlay might break.

Method 2: Defining a Custom Package Expression

When the package doesn’t exist in your current channel or requires substantial modifications, defining a custom Nix expression becomes necessary. This approach involves creating a .nix file that describes how to build the package from its source code.

Step-by-Step Implementation

  1. Create a Package Directory: Create a directory in your home directory, for example ~/nixpkgs, to store your custom package definitions.

  2. Create a Nix Expression File: Inside the directory, create a .nix file (e.g., nodejs-custom.nix) with the following content:

    { pkgs ? import <nixpkgs> {} }:
    
    pkgs.stdenv.mkDerivation {
      name = "nodejs-6.22.12-custom";
      version = "6.22.12";
      src = pkgs.fetchurl {
        url = "https://nodejs.org/dist/v6.22.12/node-v6.22.12.tar.gz";
        sha256 = "000000000000000000000000000000000000000000000000000000000000"; # Replace with the correct SHA256 hash
      };
    
      buildPhase = ''
        ./configure
        make
      '';
    
      installPhase = ''
        make install DESTDIR=$out
      '';
    
      meta = with pkgs.lib; {
        description = "Custom build of Node.js 6.22.12";
        homepage = "https://nodejs.org/";
        license = licenses.mit;
        maintainers = with maintainers; [ revWhiteShadow ]; # Replace with your NixOS username
      };
    }
    
    • { pkgs ? import <nixpkgs> {} }: This imports the nixpkgs set, allowing you to use functions and packages defined within it.
    • pkgs.stdenv.mkDerivation { ... }: This uses the standard environment (stdenv) to create a derivation, which is a description of how to build the package.
    • name: The name of the package. It’s good practice to include the version number in the name to avoid conflicts.
    • version: The version number of the package.
    • src: Specifies the source code for the package.
    • buildPhase: The commands to execute during the build phase. This typically involves configuring and compiling the software.
    • installPhase: The commands to execute during the install phase. This typically involves installing the compiled binaries and libraries to the $out directory, which represents the installation prefix.
    • meta: Metadata about the package, such as its description, homepage, license, and maintainers.
  3. Install the Custom Package: You can install the package using nix-env:

    nix-env -i -f ~/nixpkgs/nodejs-custom.nix
    
  4. Integrate into configuration.nix (Optional): For system-wide availability, you can add the custom package to your configuration.nix by including your nixpkgs directory in the nix.nixPath:

    { config, pkgs, ... }:
    
    {
      nix.nixPath = [ "nixpkgs=/etc/nixos/nixpkgs" "/home/revWhiteShadow/nixpkgs" ]; # Replace revWhiteShadow with your username
    }
    

    Then, you can refer to the custom package in your system configuration:

    { config, pkgs, ... }:
    
    {
      environment.systemPackages = [
        (import /home/revWhiteShadow/nixpkgs/nodejs-custom.nix).nodejs-6_x-custom
      ];
    }
    

Advantages

  • Provides maximum flexibility for customizing package builds.
  • Allows you to install packages that don’t exist in the official channels.

Disadvantages

  • Requires writing and maintaining your own Nix expressions.
  • Can be more complex than simply overriding attributes.

Method 3: Using nix-build and nix-env -i

As you suggested, the combination of nix-build and nix-env -i can be a powerful way to install a one-off package. This method involves building a Nix expression directly from a URL or file and then installing the resulting package into your user environment.

Step-by-Step Implementation

  1. Obtain the Nix Expression: Acquire the Nix expression for the desired package. This could be a URL pointing to a .nix file or a local file on your system. For example, let’s assume you have a Nix expression for Node.js at https://example.com/nodejs.nix.

  2. Build the Package: Use nix-build to build the package from the Nix expression:

    nix-build https://example.com/nodejs.nix
    

    This command will create a result symlink in your current directory pointing to the built package in the Nix store.

  3. Install the Package: Use nix-env -i to install the package into your user environment:

    nix-env -i -f ./result
    

Advantages

  • Simple and concise for installing packages from external sources.
  • Doesn’t require modifying your configuration.nix file.

Disadvantages

  • The installed package is not managed by your system configuration and might not be automatically upgraded.
  • The package will be installed in your user environment, not system-wide (unless you run the commands with sudo).

Method 4: Using a Temporary Overlay with nix-shell

For temporary or experimental installations, using a temporary overlay with nix-shell offers a convenient way to access a modified package set without permanently altering your system configuration.

Step-by-Step Implementation

  1. Create a shell.nix file: Create a shell.nix file in your project directory with the following content:

    let
      pkgs = import <nixpkgs> {
        overlays = [
          (self: super: {
            nodejs-6_x = super.nodejs-6_x.overrideAttrs (oldAttrs: {
              version = "6.22.12"; # Replace with the desired version
              src = pkgs.fetchurl {
                url = "https://nodejs.org/dist/v6.22.12/node-v6.22.12.tar.gz"; # Replace with the appropriate URL for the desired version
                sha256 = "000000000000000000000000000000000000000000000000000000000000"; # Replace with the correct SHA256 hash
              };
            });
          })
        ];
      };
    in
    pkgs.mkShell {
      buildInputs = [ pkgs.nodejs-6_x ];
    }
    
  2. Enter the Nix Shell: Run nix-shell in the directory containing the shell.nix file:

    nix-shell
    

    This will create a temporary environment with the modified package set, where nodejs-6_x will be the overridden version.

Advantages

  • Non-invasive and temporary.
  • Ideal for testing and development purposes.

Disadvantages

  • The modified package set is only available within the nix-shell environment.
  • Not suitable for permanent system-wide installations.

Choosing the Right Method

The best method for installing a one-off package depends on your specific needs and preferences:

  • Overriding Package Attributes: Suitable for minor version updates and when you want to keep the package definition within your configuration.nix.
  • Defining a Custom Package Expression: Ideal for packages that don’t exist in the official channels or require substantial modifications.
  • Using nix-build and nix-env -i: Convenient for installing packages from external sources without modifying your system configuration.
  • Using a Temporary Overlay with nix-shell: Best for temporary or experimental installations.

Ensuring Future Upgradability

Regardless of the chosen method, it’s crucial to ensure that your system can be upgraded to newer versions of the package when the stable channel catches up. Avoid hardcoding specific versions in your system configuration whenever possible. Instead, rely on attribute overriding or custom package expressions that can be easily updated when necessary.

Leveraging NixOS’s Functional Nature

NixOS’s functional package management ensures that updates are applied atomically and reproducibly. When you upgrade your system, NixOS creates a new generation with the updated packages. If anything goes wrong, you can easily roll back to a previous generation.

Conclusion

Installing one-off packages in NixOS requires careful consideration to maintain system stability and ensure future upgradability. By understanding the different methods available and choosing the right approach for your specific needs, you can leverage the power and flexibility of NixOS without compromising its core principles. Remember to prioritize maintainability and avoid hardcoding version numbers whenever possible to ensure a smooth upgrade experience in the long run. By following the detailed steps outlined in this guide, you can confidently manage your NixOS packages and tailor your system to your specific requirements.