Improving the Nixos Experience
Table of Contents
This is a follow-up to my previous post on Nixos for WSL: Link to part one: Intro to Nixos for WSL. In this post, we will go over some quality of life changes I made to my WSL instance.
nixos-rebuild
via git (using Github)
Using the flakes files locally can be limiting if we want to mirror the configuration to multiple distributed machines. Using a system like git will allow us to keep one source of truth for our configuration.
Directory structure for git
To ensure nixos-rebuild
can find our flake.nix
entry point, we want it to be on the root directory of the git repo. All other flakes can be stored in nested directories. Just ensure to use the path relative to the calling file.
Here is an example using my configuration.
https://github.com/username/repo.git#hostname
|
|-flake.nix
|-modules
|-general-wsl.nix
|-docker.nix
|-nixdev.nix
|-nixdev-tun.nix
Pull configuration via git (Public)
We can utilize nixos-rebuild
to pull our configuration directly from Github. We have a few methods that are determined by your use case. For all of these iterations, I will use the --refresh
switch. This ensures nixos pulls from the github repo and not from cache.
# HTTPS
sudo nixos-rebuild switch --flake https://github.com/<username>/<repo>#<hostname> --refresh
# SSH
sudo nixos-rebuild switch --flake git+ssh://github.com/<username>/<repo>#<hostname> --refresh
Pull configuration via git (Private)
Due to the private access, I access the repo via git+ssh. For this to work properly, we need an SSH access key for the repo or your github account.
Secure SSH Private key
Once we have this, we need to import it into /root/.ssh
and set a configuration in a config
file. Ensure to run chmod 600 <sshkey>
as openssh will not work if the key permissions are too open.
Create config file /root/.ssh/config
Host github.com
Hostname github.com
IdentityFile "/path/to/private/key"
Run in a shell
# SSH
sudo nixos-rebuild switch --flake git+ssh://github.com/<username>/<repo>#<hostname> --refresh
Creating a shortcut (alias)
Using environment.shellAliases
in a nix flake, we can setup a shortcut to update our configuration. Our shortcut will be nixos-refresh
.
{ pkgs, lib, config, ...}: {
environment.shellAliases = {
nixos-refresh = ''
sudo ${lib.getExe pkgs.nixos-rebuild} switch --flake git+ssh://github.com/<username>/<repo>#${config.networking.hostName} --refresh
'';
}
}
To keep this block flexible, we used the two functions within Nixos.
${lib.getExe pkgs.nixos-rebuild}
represents the current path to nixos-rebuild’s binary. Our shell may not have the proper paths to this binary at the time of rebuild.${config.networking.hostName}
will input whatever value our hostname is, as defined within this configuration.
We will need to run our nixos-rebuild ...
command as before, then this alias command will become available to us.
Update Nixos on boot
Now that we know how to rebuild from remote on-demand, we should also rebuild on reboot incase it has been a while. For this, we will create a systemd service that triggers on boot. Our service will be called rebuild
or rebuild.service
.
{ pkgs, lib, config, ...}: {
systemd.services.rebuild = {
script = "${lib.getExe pkgs.nixos-rebuild} switch --flake git+ssh://github.com/<username>/<repo>#${config.networking.hostName} --refresh";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ git openssh ];
restartIfChanged = false;
};
}
Lets break this down:
systemd.services.<service-name>
is how we define the name of our service.script
this is what will trigger on service activation. In this case, it is the same command snippet we used fornixos-refresh
. The only difference is that we do not need to usesudo
as a systemd service.after
defines what services need to be initialized prior to this triggering.wantedby
defines what services will await this service starting.path
path variable that the service will have access to. In this case, we usedwith pkgs; [ git openssh ];
as we need those two packages for this service to run.restartIfChanged
determines if the script should be restarted on change.
Once this is set, we can run nixos-refresh
or nixos-rebuild ...
to create this service.
Determine if service is running on shell creation.
If you are using the system immediately after boot, you may run into a race condition where the rebuild process has not finished yet. To aid in this, we will add a verbose output and alias to allow us to query the state of rebuild.service
.
shellAlias
{ pkgs, lib, config, ...}: {
shellAliases = {
nixos-status = ''state=$(systemctl is-active rebuild); color=$([ "$state" == "active" ] && echo "\e[31m" || echo "\e[32m"); echo -e "Rebuild.Service (nixos-rebuild) $color$state\e[0m"'';
};
}
This creates an alias for us nixos-status
that queries the state of the systemd service. Here is another example of the code we are using for the alias.
state=$(systemctl is-active rebuild);
color=$([ "$state" == "active" ] && echo "\e[31m" || echo "\e[32m")
echo -e "Rebuild.Service (nixos-rebuild) $color$state\e[0m"
The output for this will change color depending on the state.
Active
== RedInactive
== Green
shellInit
{ pkgs, lib, config, ...}: {
shellInit = ''
state=$(systemctl is-active rebuild)
[ "$state" == "active" ] && echo -e "Rebuild.Service (nixos-rebuild) \e[31m$state\e[0m"
'';
}
This configuration will only display an output if the state is Active
.
With this setup, we can BOTH proactively query the state via nixos-status
and reactively be alerted of an ongoing rebuild via environment.shellInit
.
Summary
From here, you should be able to keep a unified and interchangeable configuration for Nixos. This can be applied to BOTH physical machines and WSL instances.
Written by James Immer