Tracking my internet speed with nix and systemd: Part I
I recently upgraded to gigabit internet through Verizon FiOS, for basically no other reason than that having those sorts of download speeds is quite thrilling. Initially, the speeds were pretty comparable to what was advertised, but I wanted to set up a recurring job to start tracking them over time both to make sure it stayed consistent but also out of genuine curiosity about the variability of the network. I’d also been meaning to dig into nixos modules for quite some time, and this seemed like the perfect opportunity to do so.
The workflow for modules is pretty simple. Basically, modules
expose a set of knobs that can be set in your nixos configuration, that then push functionality into the resulting materialized system.
The official docs in this case are pretty illuminating, but I’ve trimmed their example a bit just to highlight the basic enable + mkIf enable ...
workflow typical to modules.
{ lib, pkgs, config, ... }:
{
options = {
services.hello = {
enable = lib.mkEnableOption "hello service";
};
};
config = lib.mkIf config.services.hello.enable {
systemd.services.hello = {
wantedBy = [ "multi-user.target" ];
serviceConfig.ExecStart = "${pkgs.hello}/bin/hello -g'Hello, World!'";
};
};
}If you’re not used to nix, the fact that this works at all is I think pretty amazing, and maybe just slightly unhinged. Basically the module
provides two attributes, options and config. options tells the module system which settings to expose in your root configuration, and then
config actually pushes stuff into that configuration, usually by relying on an enable flag in options, but this is just convention.
After saving this in hello-world.nix, you can pull it in like so:
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs";
outputs = { self, nixpkgs, ... }:
{
nixosConfigurations."desktop" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hardware-configuration.nix
./hello-world.nix
{
services.hello.enable = true;
}
]
};
};
}and now after a nixos rebuild you’ll have a fully configured systemd service, which you can inspect with systemctl status hello.service and journalctl -u hello.service.
As with most things nix, I will admit to not knowing several critical details of how this all fits together. For example, the { lib, pkgs, config, ... }
part of the module is a bit magical to me, and I’m not even entirely sure what those pkgs really are, or what config is referring to.
I’ve taken to copy/paste-ing a bunch with nix, and true nirvana still evades me, but I have found it to be incredibly powerful despite these nagging
uncertainties.
So using this basic approach, I wanted to set up a systemd service that periodically invokes speedtest, and sends the results to my prometheus instance. Luckily, speedtest-cli already exists, and invoking it is straightforward (though you
do need to add --secure or it won’t work). Luckier still,
speedtest-cli already exists in nixpkgs.
To pull in the package, you use a bit of nix magic via python3.withPackages which gives you a python interpreter that already has the packages installed
in its site-packages/ directory.
{ lib, pkgs, config, ... }:
let
python-env = pkgs.python3.withPackages (ps: with ps; [ speedtest-cli ]);
in
{
options.services.speedtest = {
enable = lib.mkEnableOption "speedtest service";
};
config = lib.mkIf config.services.speedtest.enable {
systemd.services.speedtest = {
wantedBy = [ "multi-user.target" ];
serviceConfig.ExecStart = "${python-env}/bin/speedtest-cli --secure";
};
};
}After referencing the configured python interpreter in our interpolated ExecStart, nix pulls in the python package
and enables us to reference the executable that ends up in the bin/ directory of that store path.
There’s a lot going on here, but ultimately what’s happening feels like how things should work, in a way that makes me question why everything else
is so complicated. python3.withPackages involves a lot of nix that I don’t understand, but conceptually I understand the resulting DAG, and the fact
that that entire DAG is pulled in through this string interpolated systemd command is pretty remarkable.
See Part II for setting up prometheus and pushing the results.