Advices on how to create and test a module

Hi all

I would like to package a set of binaries as a module for nixos.
The binaries are the asusctl services (https://asus-linux.org/).

I already created the package named asusctl that successfully build the binaries. Now I would like to integrate it as a module in nixpkgs allowing a simple
config.services.asusctl.enable = true to install everything and especially the systemd related .service files and the udev rules.

This module will relies on the asusctl package I already created. I think I will take an example inside the nixpkgs/nixos/modules/services directory to start.
However I was wondering how I can test it on my machine.

My configuration is based on a flake and to create and check the package I simply used the nix-build in the directory containing the default.nix file.

I am wondering how I can test a module though as I guess I would need to integrate it into my current flake which does not use the clone of the nixpkgs repository I use as a working directory.

Any advice to give me in order to achieve this ?

Flakes make this really easy, actually, as long as you’re willing to build off your dev branch. In your flake’s inputs set:

{
  inputs = {
    nixpkgs.url = "path:/absolute/path/to/your/nixpkgs";
  };

  # ...
}

and it’ll build your system with your modified nixpkgs :slight_smile:

You can also use nixos-rebuild build-vm to test it in a VM if you don’t want to use switch and deploy it to your running system.

Without flakes it should be something like nixos-rebuild -I nixpkgs=/absolute/path/to/your/nixpkgs test

1 Like

Heh, you made me realize, many roads lead to Rome, the pure-CLI equivalent of that with flakes is:

nixos-rebuild <switch/test/build-vm> --override-input nixpkgs /absoulte/path/to/your/nixpkgs --flake <flake>

Probably nicer for a one-off.

The nixpkgs manual documents a way to only import specific modules, too, but it’s quite complex, I only did it once and then gave up on rebuilding only what I want to test: Nix - NixOS 22.05 manual

1 Like

wow, that’s way more handy than changing the flakes file and updating the lock, and then reverting when finished

1 Like

Thanks for the answer doing this enabled me to test it !

However I am facing now another issue. The program I am building is written and rust and need rust compiler 1.61.0. When I use the rustPlatform.buildRustPackage function it seems to use the 1.60.0 from the nixpkgs master branch and fails.

Is there a way to change the version of the rust compiler used in this function for a particular package ? I would like to integrate this in the official nixpkgs in the end so I shall do it as clean as possible.

I’ve put the commit here:

I guess I shall be able to use the overlay in some way to change the rust compiler but don’t know exactly how for a package.

Ok I solved the cleaning everything and rebuilding on master.

Now my problem is how to deal with the files stored in etc.
The program need a configuration file to be stored in /etc to start.
I guess I have 2 solutions:

  • either patch the source code to get it from the $out/etc
  • find a way to use the /etc/ directory through /etc/static

I don’t know what is the best practice in such cases.

As an user / admin, I prefer services to use symlinks in /etc so it’s easier to look for configuration files :slight_smile:

As an user / admin, I prefer services to use symlinks in /etc so it’s easier to look for configuration files

I second this. Take nginx as an example, where you need to do systemctl status nginx, then take the config file from ExecStart and load that into your editor of choice, as opposed to simply cat /etc/some_random_file.conf.

As a matter of principle, if the software looks by default in /etc, please drop the file there:

{
  environment.etc."some_random_file".source = pkgs.writeText "file.conf" ''
    foo=bar
  '';
}

Or of course use one of the very convenient helpers under pkgs.formats that allows simply passing an attrset.

netdata module is a nice example of being able to use an attrset for its global config, but also offer to write files in its config dir using pkgs.writeText :+1:t3:

Thank for the hints!

If I understood correctly, I can keep the asusctl package I wrote for installing the binaires and uses the asusctl module to generate the config files in /etc/asusctl where the app originally search for its config. This way I don’t have to patch the source to get the config somewhere else.

Then I’ll use the pkg.formats to map an attrset to the config files format. One is a toml file and the second is an xml so I think it will be easy.

It is worth pointing out that this makes reproducibility harder; Other applications may read that file, and then suddenly no longer work on another machine where you don’t use this option.

As a paranoid user, I therefore prefer doing it the other way :wink: Ideally through a hardened systemd service that uses ConfigDir or whatever it is to pass the config file.

Minor details, but I feel using /etc is generally a bit less clean, albeit convenient. I’d agree that it’s not worth patching the code to get that, though.

It is worth pointing out that this makes reproducibility harder; Other applications may read that file, and then suddenly no longer work on another machine where you don’t use this option.

I don’t follow you.

Take nginx as an example. It doesn’t matter if it is run by doing nginx -c /nix/store/blabla-nginx.conf or just nginx with /etc/nginx/main.conf then being a symlink to /nix/store/blabla-nginx.conf.

From nginx’s point of view, it’s the same thing.

Now, if your point is that something else might read /etc/nginx/main.conf and choose to do something with that, then sure - but how realistic is this? And what’s the downside? It any case it’s a symlink to what you want the nginx configuration to be.

It really hasn’t nothing to do with neither paranoia nor hardening.

I’m not as opinionated on this as I probably sound, it’s just a very minor architectural opinion.

I like tightly coupling configuration to its specific service, while decoupling separate services. /etc loosens that a bit, because it’s shared across all services.

The only benefits I can think of involve multiple instances of the same service, especially when coupled with the “something else” scenario, default locations changing and things like that.

Yes, getting a benefit from this this is unrealistic for most services, and most of the time will be very minor. Especially on NixOS where multi-instance things are impractical. But I like the idea of it; not having what are essentially the “global variables” of Linux systems just feels cleaner.

I call it “paranoid” because it’s not very realistic, not because I think it has any effect on security or anything. It’s unrelated to hardening, too, but systemd does have ConfigurationDirectory= (which actually is a more practical small benefit; that setting is easier to use or unnecessary to begin with).

So I have updated my package. Now the files contained in /etc/asusd are generated using the variables of the service.

However I still have 2 problems though:

  • The asusd binary fails to open the /etc/asusd/profile.conf file in R/W as the symlink points to the RO nix store. How can I initialize the file properly using the variable of the service and set the R/W permissions on it ?

  • Second problem, the binary rog-control-center seems to need X11 but I am using wayland. It complains about not finding libX11.so. I think it shall work with xwayland but not sure how to modify the package to let the binary using it. I am trying to add it as a buildinputs to see if it solves the issue.

I think I have a version of the service which is working.

My last issue is that I am unable to run the rog-control-center binary because it does not find the libGL or libEGL despite having put it in the buildInputs.

What am I missing here ?

Hosted by Flying Circus.