This is the NixOS ❄️ configuration that I use on my machines.
Configure your setup first by populating a config.mk with at least TARGET
set to whichever target from the Flake configuration you want to install. An example config.mk is presented below:
TARGET = .\#dell-xps-9360
NIXOS_REBUILD_ARGS = --show-trace --verbose
Run make
to install the Flakes-based configuration on your setup.
Show flake setup with:
nix flake metadata
Update either of the inputs with:
nix flake lock --update-input INPUT
Flakes provide a mechanism for managing dependencies through a lock file that pins all dependency versions and allows for easier tooling to update these dependencies (using nix flake lock --update-input
or nix flake update
).
The use of
nixos-rebuild
without flakes would require one to prepare the configurations directory (/etc/nixos) while also explicitly pointing towards the nixpkgs rev to build against.
Within the Flake, multiple configurations are defined to correspond to the different build targets (devices) and one can specify one of these configurations build a NixOS system for through the --flake
CLI option.
nixos-rebuild test --flake '.#dell-precision-5560' --use-remote-sudo --show-trace --verbose --impure
For convenience, the Make rule all
, test
or switch
are defined to avoid us having to type too much whenever we want to roll an update, the rules mean the following:
test
: runnixos-rebuild test
and evaluate a configuration such that it can be tested without producing a new generation (with the caveat that some changes may require a reboot to properly test after all)switch
: runnixos-rebuild switch
to evaluate a configuration and produce a new generation such that it can be selected as an option to boot fromall
: alias toswitch
See https://nixos.wiki/wiki/Flakes for more information.
- Update
/etc/nixos/configuration.nix
to specify the installation ofnixFlakes
and enable the necessary experimental feature flag.{ pkgs, ... }: { nix = { package = pkgs.bleeding.nix_2_13; extraOptions = '' experimental-features = nix-command flakes ''; }; }
- Then simply execute
sudo nixos-rebuild test
to apply the configuration change.
In our pre-Flakes previous approach, we copied the needed nix files into
/etc/nixos
through themake nonflake-setup
rule before applying these changes through anixos-rebuild
run. With the use of Flakes, we don’t have to think about explicitly managing/etc/nixos
anymore and the change proposed to/etc/nixos
in this section should be the only change you’d ever need to make to/etc/nixos
. If you have never worked with the previous configuration (with the different make rules that we cooked up), consider yourself fortunate and just forget that we even mentioned it. 😌
The more reliable way to get flakes working on a system is to follow the instructions for Installing Flakes on NixOS. This section documents my toying around with different ways to get flake capability in my setup, but these didn’t ever work without pain on my end – which is only due to my lack of understanding of Nix and not nix itself obviously. Here be dragons. 🐉 Everything here may feel like bandaids.
Flakes are experimental as of time of writing (2021.11.05). Use nix inside of a nix-shell to access the flakes feature and then prefix your nix commands with sudo
to deal with the trusted user issue in case you encounter it.
nix-shell --packages nixUnstable
sudo nix --experimental-features 'nix-command flakes' flake check
Use of nixos-rebuild within a nix-shell may be problematic and when attempts to run nixos-rebuild outside of a nix-shell results to error: unrecognised flag '--extra-experimental-features'
, one may consider installing nixUnstable
with nix-env
as follows:
nix-env -f '<nixpkgs>' -iA nixUnstable
Note that this section already outlined two ways to try to get access to nixUnstable. If you simply want to be able to run a nix flake check
on a machine that doesn’t have the flakes feature flag enabled, it may be more than fine to just fire this up inside of a nix-shell. Calling nixos-rebuild
from the shell, however; proved tricky to me and I am too lazy to really figure out why. My hunch is that there are some components of the ecosystem that are now installed from the nixFlakes pacakge, while there are still some tools in the call-chain that have no idea how to properly deal with the experimental-features flag. As such, following the instructions from the wiki and (in the previous section) will guarantee you considerably less pain. Trust me, it’s worth it.
f = builtins.getFlake (toString ./.) # Look at inputs, e.g.: nixpkgs f.inputs.nixpkgs # Look at output, e.g.: nixosConfiguration f.outputs.nixosConfiguration
💡 The last time I actually used the non-Flake approach, I tested it with
make nonflake-setup && make nonflake-remote-test
and applied the configuration withmake nonflake-setup && make nonflake-remote-switch
(actually,make nonflake-setup
only needs to be run once so if you have run it before firing either of the test stages, then a subsequent switch may be triggered without firing the setup stage again). This approach just copies the nixos-configuration into/etc/nixos
in the setup stage and then builds it against a nixpkgs target specified by the repo URIMY_NIXPKGS_REPO
and the commit hashMY_NIXPKGS_COMMIT
in the test and switch stages.The difference between the test and switch stages is that the switch stage actually commits a new generation to your system. The design assumes that the GitHub archive URL scheme applies so this may break if
MY_NIXPKGS_REPO
points to a non-GitHub-like forge.
This NixOS configuration comes packages with a collection of make rules that are intended to make your life a little bit easier. The installation process for a NixOS configuration requires the making of changes to /etc/nixos
followed by the installation of the given configuration by nixos-rebuild test
or nixos-rebuild switch
. Testing a configuration is convenient to test changes that you aren’t sure you may want to persist yet. After playing around with the new configuration for a while one can persist the changes by running a switch operations. 😉
RunIn both
nixos-rebuild test
as innixos-rebuild switch
all changes are actually installed. The only difference is thatnixos-rebuild switch
persists the changes by adding a new generation that shows up in the bootlist. In the test case the installed changes are in your nixos store until you clean it up. This is why subsequentnixos-rebuild
operations are much faster – a lot of data is cached from a previous build. 😉
make nonflake
: which sets up/etc/nixos
and callsnixos-rebuild switch
to make the configuration permanent 😉- profit 🏆
make nonflake-setup
to copy the configuration to/etc/nixos
make nonflake-*-test
to install the configuration without producing a generation such that all changes are generally undone after rebootmake nonflake-*-switch
to commit the configuration to a generation such that all changes will still be around after a reboot 😉- profit 💰
nonflake-setup
rule sets up the/etc/nixos
directory to be able to runnixos-rebuild
at a later stagetest
rules perform anixos-rebuild
without committing it to the generationsswitch
rules perform anixos-rebuild
and commit it to the generations
of which “test” and “switch” rules take the following form:
nonflake-test
andnonflake-switch
, referred to as the “basic” or “vanilla” rulesnonflake-local-test
andnonflake-local-switch
, referred to as the “local” rulesnonflake-remote-test
andnonflake-remote-switch
, referred to as the “remote” rulesnonflake-upgrade-test
andnonflake-upgrade-switch
, referred to as the “upgrade” rules
Note that the vanilla rules (nonflake-test
and nonflake-switch
) are basically performed against the nixpkgs version of your system’s selected channel (see nixos-version --revision
). This could be updated by executing sudo nix-channel --upgrade nixos
.
Conversely, if you don’t want to think about manually upgrading, you could use the nonflake-upgrade-test
and nonflake-upgrade-switch
rules which will install against the latest version of your selected channel. Between different nixos-rebuild operations, one should expect that occasionally packages can be removed or renamed thus resulting to failing builds, but this should be simple to fix.
The nonflake-local-test
and nonflake-local-switch
rules are useful if you need to build against a local clone of nixpkgs. This comes in handy when you’ve added, altered or removed modules or packages in nixpkgs. When using this approach, one should occasionally consider consolidating the updated upstream branch for the given channel with the local repository (either through a merge and/or rebase).
Finally, nonflake-remote-test
and nonflake-remote-switch
could be used to build against a remote nixpkgs archive. This is convenient if you want to build your configuration against a known endpoint between different machines. If the remote endpoint is that of a branch, and you are not the maintainer of that endpoint you will have to exercise the same caution that you practiced when invoking noflake-upgrade-test
or nonflake-upgrade-switch
rules since packages could be removed or renamed between revisions.
The optional personal.nix file is included if it exists.
Use it to capture personal details of your configuration that are not as interesting or too sensitive to track into version control. Observe the following snippet for a sense of what I decided to track in this file:
{ config, pkgs, ... }:
{
<<config-time>>
}
Currently the personal.nix is imported by referring to the full path ~/home/vidbina/nixos-configuration/personal.nix which will warrant changes on your system. Since Flake-based configurations don’t allow referring to files that aren’t tracked in git, I had to refer to personal.nix by its full path and enable the impure flag when invoking nixos-rebuild
which is a design smell.
Time zones can be configured on a NixOS level through the time.timeZone
variable.
# Set your time zone.
time.timeZone = "Europe/Berlin";
# Example values:
# America/Los_Angeles
# America/Mexico_City
# America/New_York
# America/Paramaribo
# America/Puerto_Rico
# Asia/Bangkok
# Asia/Seoul
# Asia/Tokyo
# Europe/Amsterdam
# Europe/Berlin
You can use the time.timeZone
setting above to manage the time zones or edit the ~/.profile file to export the TZ
variable as demonstrated in the statement below.
TZ='America/Puerto_Rico'; export TZ
Configuring time as part of the system configuration may require you to produce a new [NixOS] generation simply to apply a timezone change. I’ve looked for ways to make time zone changes through home-manager or in a manner less “intrusive” but it seems that the NixOS configuration is the way to do this for now. 🤷🏿♂️
Obtaining valid values for timezones can be interactively solved using the tzselect
command which, through a series of interactive prompts, obtains the information about your time zone and provides the correct TZ value as a response.
Getting a glimpse of the date or time in a particular region or timezone can be accomplished by setting TZ
prior to calling date as in the examples below:
TZ='America/Puerto_Rico' date
TZ='CEST' date
In order to configure OpenVPN, override the openvpn
configuration in net.nix to comply with the following format:
{ servers = { tcp-config-one = { autoStart = false; updateResolvConf = true; config = '' config /home/user/path/to/openvpn-config-for-one.ovpn auth-user-pass /path/to/myprovider-pass-file.txt ''; }; }; }
where the paths for config and auth-user-pass are updated to reflect the paths of the files on your system.
Alternatively, leave the helpers defined in the let
block of the openvpn attribute in net.nix as is and provide a config/openvpn.nix file with the configuration as follows:
{ toUpper }: let regions = [ ["de" "Germany"] ["nl" "Netherlands"] ["us-nyc" "USA-NEW-YORK"] ]; builder = { region ? [], kind ? "tcp" }: let locationIdentifier = builtins.elemAt region 0; locationName = builtins.elemAt region 1; in { handle = "${toUpper(kind)}-${toUpper(locationIdentifier)}"; configFile = "/home/user/path/to/${kind}-openvpn-config-for-${locationName}.ovpn"; passFile = "/path/to/myprovider-pass-file.txt"; }; in builtins.foldl' (acc: val: acc ++ [(builder { region = val; kind = "tcp"; })]) [] regions ++ builtins.foldl' (acc: val: acc ++ [(builder { region = val; kind = "udp"; })]) [] regions
in order to dynamically generate your configuration in case you have many configurations that share some common properties.
The example above, generates a configuration of the following OpenVPN configurations with their corresponding .ovpn files:
tcp-DE
at/home/user/path/to/tcp-openvpn-config-for-Germany.ovpn
tcp-NL
at/home/user/path/to/tcp-openvpn-config-for-Netherlands.ovpn
tcp-US-NYC
at/home/user/path/to/tcp-openvpn-config-for-USA-NEW-YORK.ovpn
udp-DE
at/home/user/path/to/udp-openvpn-config-for-Germany.ovpn
udp-NL
at/home/user/path/to/udp-openvpn-config-for-Netherlands.ovpn
udp-US-NYC
at/home/user/path/to/udp-openvpn-config-for-USA-NEW-YORK.ovpn
where all configurations share the same passFile and naming scheme such that we’re able to derive the necessary attributes from a smaller collection of inputs.
In summary, config/openvpn.nix
contains a function that receives some functions needed for the internal housekeeping and simply returns a list of attrsets. In the provided example, we just needed to provide the toUpper
helper and then just fold over a list of regions to generate the list for the helper in net.nix. In case this is just too messy for you, revert to the instructions at the head of this paragraph for a much easier but possibly more verbose setup. 😉
By simlinking the overlays directory to file:~/.config/nixpkgs/overlays.
For convenience, you may use the nixos-options
tool to introspect the actual configuration of your current system.
The following command demonstrates how one could go about recursively checking all options within time time
option.
nixos-option -r time
While debugging my TLP configuration, I often looked up the services.tlp
to figure out how the nixos-hardware along with my own settings merged in my actual config.
nixos-option -r services.tlp
Note that OpenGL drivers are listed in file:/run/opengl-driver/lib/dri.