NixOS is a Linux distribution built around the nix
package manager. The configuration of the entire system is
declarative
In theory, NixOS is the easiest
Linux distribution to use because of its declarative nature. In
theory…
allowing for immutability and reproducibility.
Let’s take a look at managing a user’s package environment declaratively in
NixOS version 20.03. The
nature
The nix
expression language is not particularly difficult. It’s the scope and
flexibility of the abstraction layer that makes the initial NixOS learning
experience painful.
of NixOS dictates that there are
numerous declarative methods. We can use attribute sets, build environments, or
even community
tools
Home manager is a system
that manages a user’s environment. It’s similar to the system’s
configuration.nix setup.
like
home manager.
Declarative Package Management
We will
combine
the command
nix-env
with the user’s config.nix to achieve a declarative
package setup. The user specific configuration is located at
~/.config/nixpkgs/config.nix. Create this file if it does not exist.
The user’s nixpkgs configuration config.nix places us in a context
similar
This may be
familiar to those who
configure the system from configuration.nix.
to nixpkgs.config. Populate config.nix with multiple build environments and
install packages using nix-env. This can
be achieved more optimally with nix flake
or you could use
nix-shell
for projects and temporary environments. The following is a basic config.nix
that makes
use
Beware of package name collisions that result in infinite recursion. Notice
that Awesome (with a capital) is used instead of awesome in one of the
overrides.
of package
overriding.
Check out my config.nix
here.
nix
{
allowUnfree = true;
packageOverrides = pkgs: with pkgs; {
Awesome = pkgs.buildEnv {
name = "awesome";
paths = [
awesome
lxappearance
paper-gtk-theme
];
};
Golang = pkgs.buildEnv {
name = "golang";
paths = [
go
];
};
PHP = pkgs.buildEnv {
name = "php";
paths = [
php74
];
};
C = pkgs.buildEnv {
name = "c";
paths = [
gnumake
meson
ninja
gcc
];
};
};
}Install the above build sets in config.nix using nix-env -Air. The -A flag
is for the attribute path, -i to install the packages and -r for removing
all
undeclared
This is
like running an
apt install or pacman -Syu that intelligently erases all user packages
before installing.
packages.
shell
nix-env -Air nixos.Awesome nixos.Golang nixos.PHP nixos.CDistribution independent installations of nix default to the nixpkgs
prefix
Each package contains the prefix
nixos. This is the default NixOS channel containing the packages. Run
nix-channel --list to view the current user’s channel
prefixes.
as the default channel name.
shell
nix-env -Air nixpkgs.Awesome nixpkgs.Golang nixpkgs.PHP nixpkgs.CQuerying the installed packages with nix-env -q shows that there are four
packages installed each containing the programs declared in config.nix.
shell
$ nix-env -q
awesome
c
golang
phpOne could exploit the recursive aspect and group packages further by any metric. Below we add another set that groups the above declarations by machine.
nix
{
allowUnfree = true;
packageOverrides = pkgs: with pkgs; rec {
Machine1 = pkgs.buildEnv {
name = "machine1";
paths = [ Awesome Golang PHP C ];
};
# Package declarations for Awesome, Golang, PHP, & C ...
};
}
Installing
This should evaluate quicker than
the first install command.
and querying this package with
nix-env shows that there is one package installed containing a program set of
other program sets declared within config.nix.
shell
$ nix-env -Air nixos.Machine1
$ nix-env -q
machine1This is immensely beneficial for use cases that prioritize keeping root system
build times
small.
Faster configuration
switches.
Users can install packages independent of the
system’s configuration.nix declaratively. The nix store also allows
selectively
This gives the option of only
exposing the binaries from /bin.
exposing program paths to the
user’s environment.
nix
{
allowUnfree = true;
packageOverrides = pkgs: with pkgs; {
Awesome = pkgs.buildEnv {
name = "awesome";
paths = [
awesome
lxappearance
paper-gtk-theme
];
pathsToLink = [ "/etc" "/share" "/bin" ];
};
};
}Instantly
remove
There’s actually no need to remove or uninstall packages in declarative package
management. The main idea is to either
rollback
bad changes or
delete
and
switch
to newer or older generations.
all packages with nix-env -Air by switching to an empty environment.
shell
nix-env -AirList previous environment switches with --list-generations. You can further
manipulate the environment lineage by using the generation arguments in the
nix-env manual.
shell
$ nix-env --list-generations
368 2021-08-13 23:16:36
369 2021-08-22 00:01:38
370 2021-08-22 00:05:47
371 2021-08-22 00:23:03Unstable Declarative Package Management
The above is nice and all, but what if a package is not in the nixos stable
channel? We can add the
unstable
The unstable
channel is based on the master repository
and contains more packages than the stable channel.
channel from
the system side, but what happens if its too unstable?
Since this is NixOS — we can mix and match. In config.nix bring in the
unstable channel directly. We
install
This
is wonderful. Getting a quick and clean install of programs like
Ungoogled Chromium
isn’t so easy in other environments.
ungoogled-chromium from
the
unstable
The paranoid user can set any
package archive and
pin specific versions.
channel as an example.
nix
let
unstable = import (builtins.fetchTarball "https://releases.nixos.org/nixos/unstable/nixos-21.03pre265961.891f607d530/nixexprs.tar.xz") {};
# Or lock at a specific commit to stop moving targets.
# unstable = import (builtins.fetchTarball {
# url = "https://releases.nixos.org/nixos/unstable/nixos-21.03pre265961.891f607d530/nixexprs.tar.xz";
# sha256 = "1hwwb4n15bbqxnbqffq4kfb369vz65sq74p537fqdp6i4ywpqsyh"; }) {};
in
{
allowUnfree = true;
packageOverrides = pkgs: with pkgs; {
Awesome = pkgs.buildEnv {
name = "awesome";
paths = [
awesome
lxappearance
paper-gtk-theme
unstable.ungoogled-chromium
];
pathsToLink = [ "/etc" "/share" "/bin" ];
};
};
}Advanced Declarative Package Management
Here’s a scenario: You’re reading some Lua code and you
need a lua formatting tool. It looks like
LuaFormatter will do the job, but that
package is neither in the stable or unstable channel. You install luarocks
— the lua package manager, but something
odd
luaformatter attempts to compile its
submodules from the read only nix path of luarocks.
happens
when it tries to install luaformatter. What now?
Create a
derivation
This is similar to writing
a PKGBUILD or APKBUILD. The ease of writing a derivation depends on
upstream’s
build and install complexity.
for luaformatter that
config.nix can call and build to expose the package to the user’s environment.
Create a packages directory and a folder containing default.nix at
~/.config/nixpkgs/packages/luaformatter.
nix
{ lib, stdenv, fetchFromGitHub, cmake }:
stdenv.mkDerivation rec {
pname = "LuaFormatter";
version = "1.3.3";
src = fetchFromGitHub {
sha256 = "1dfqsh6v8brnwzg3lgi7228lw08qqfy4ghbjyvwn7mr82fy1xcnd";
rev = version;
repo = pname;
owner = "Koihik";
fetchSubmodules = true;
};
buildInputs = [ cmake ];
meta = with lib; {
inherit (src.meta) homepage;
description = "Code formatter for Lua";
license = licenses.asl20;
platforms = platforms.linux;
};
}Test the derivation using nix-build. Run
this
There is nothing special about this
expression. The default.nix here is in the correct form for callPackage to
understand. A derivation can contain anything — though I prefer a
default.nix to always return a package, a shell.nix to return a shell, a
module.nix a module, a flake.nix a flake, and so on.
nix
expression
This is how the nixpkgs
repository
calls most derivations.
This format sets you up for a
pull request if that’s your
thing.
with the --expr argument inside the folder containing
default.nix.
shell
nix-build -E 'with import <nixpkgs> {}; callPackage ./default.nix {}'Use the --keep-failed (-K) argument to save the temporary build folder on
failure. This allows testing fixups in the failed build environment.
shell
nix-build -K -E 'with import <nixpkgs> {}; callPackage ./default.nix {}'Call the derivation if the build succeeds with pkgs.callPackage from your
config.nix
nix
let
unstable = import (builtins.fetchTarball "https://releases.nixos.org/nixos/unstable/nixos-21.03pre265961.891f607d530/nixexprs.tar.xz") {};
in {
allowUnfree = true;
packageOverrides = pkgs: with pkgs; {
Awesome = pkgs.buildEnv {
name = "awesome";
paths = [
awesome
lxappearance
paper-gtk-theme
unstable.ungoogled-chromium
(callPackage ./packages/luaformatter/default.nix {})
];
pathsToLink = [ "/etc" "/share" "/bin" ];
};
};
}Think of a derivation as an abstraction of the typical application build
process. In the above derivation we imported stdenv — the standard
environment that contains common tools and dependencies most programs need. The
fetchFromGitHub
class
Or rather an attribute
set containing attributes.
contains methods that extend the
standard environment allowing us to pull in repositories the
nix
Nix builds in a sand–boxed environment by
default, so arbitrary network connections are not allowed outside the scope of
attributes.
way. The application luaformatter requires
cmake, so it is sourced as one of the buildInputs — another attribute
within the derivation context.
A derivation can be extended in multiple ways, allowing us to
custom
Most applications and libraries are
already built for us.
build any application. The nixpkgs
repository has an
overview
of building artifacts in popular environments like
Python.
In tricky application builds, step down a level of abstraction to fix it up
using the derivation’s
phase attributes.
Finally a derivation can be paired with a
module allowing us to hide away the above work
into a simple
set
Writing a module is a story
for another day.
of attributes that a user can easily understand. This is what most users care
about. The following is a made up example from the simple derivation above.
nix
{
programs.awesome.enable = true;
programs.awesome.luaFormat = true;
# Or
programs.awesome = {
enable = true;
luaFormat = true;
};
}
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.