I am trying to package a small tool of mine in a separate flake. The trouble i am currently having is referring to the package from my configuration module.
This is my current setup.
flake.nix
{
description = "description";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
};
outputs = {
self,
nixpkgs,
}: let
systems = ["x86_64-linux" "aarch64-linux"];
forAllSystem = f: nixpkgs.lib.genAttrs systems (system: f system);
in {
packages = forAllSystem (system: {
default = nixpkgs.legacyPackages.${system}.callPackage ./soppps.nix {};
});
nixosModules = {
soppps = import ./soppps-service.nix;
default = self.nixosModules.soppps;
};
};
}
soppps-service.nix
{
lib, config, ...
}: let
cfg = config.soppps;
in {
options = {
soppps = {
files = lib.mkOption {
type = lib.types.listOf lib.types.path;
description = ''
List of files to be processed. can use unix globs.
'';
};
package = lib.mkOption {
description = "soppps package to use";
type = lib.types.package;
};
};
};
config.systemd.services.soppps = {
wantedBy = ["sysinit.target"];
serviceConfig = {
Type = "oneshot";
ExecStart = ["${cfg.package}/bin/soppps"];
RemainAfterExit = true;
};
};
}
soppps.nix is just a simple buildRustPackage statement, so i don’t think it’s important for this since it does get packaged just fine.
The pattern with binding config.<package>
to cfg
and using cfg.package
is something i saw on all packages i have looked at (ssh from nixpkgs or sops-nix for example).
Using this in my system however (by adding inputs.soppps-nix.nixosModules.soppps
to the imports
of my configuration.nix) throws an error because no config argument was passed, which makes sense to me since it was never supplied it. I have gotten it to work by directly passing self.packages.x86_64-linux.default
but i feel this isn’t the proper way to do this. Where exactly would the config
parameter be created and passed, so that using this module only requires importing it after adding the flake?
When modules are evaluated for a nixos system, argument config
is passed to all modules by default – it’s not something that one would need to pass explicitly (outside of certain edge cases and playing with lib.evalModules
but I digress).
Fundamentally to use a custom package in a flake’s module you need the package to be available when that module will be evaluated. Two patterns for this:
-
Overlay – declare an overlay with your package and use the overlay in the target system’s nixpkgs. Then you can just reference pkgs.mypackage
in the module and pkgs
attrset will have mypackage
attribute
-
Binding at import site in flake, which I believe is what you tried:
I have gotten it to work by directly passing self.packages.x86_64-linux.default
I personally prefer this pattern as opposed to carrying around an overlay and inevitably forgetting it at some point.
An example with your flake (replacing your package with pkgs.hello
):
# flake.nix
{
description = "description";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
};
outputs = {
self,
nixpkgs,
}: let
systems = ["x86_64-linux" "aarch64-linux"];
forAllSystem = f: nixpkgs.lib.genAttrs systems (system: f system);
in {
packages = forAllSystem (system: {
default = nixpkgs.legacyPackages.${system}.hello;
});
# This is just to demo how the module works; not a real check
checks = forAllSystem (system:{
demo = nixpkgs.legacyPackages.${system}.testers.runNixOSTest {
name = "demo";
nodes.machine1 =
{ config, pkgs, ... }:
{
services.getty.autologinUser = "root";
imports = [
self.nixosModules.default
];
};
testScript = "start_all()";
};
});
nixosModules = {
soppps = import ./soppps-service.nix self; # note passing 'self' here
default = self.nixosModules.soppps;
};
};
}
# soppps-service.nix
self: # Add this
{
lib, config, pkgs, ...
}: let
cfg = config.soppps;
in {
options = {
soppps = {
files = lib.mkOption {
type = lib.types.listOf lib.types.path;
description = ''
List of files to be processed. can use unix globs.
'';
};
package = lib.mkOption {
description = "soppps package to use";
default = self.packages.${pkgs.system}.default; # Add this
type = lib.types.package;
};
};
};
config.systemd.services.soppps = {
wantedBy = ["sysinit.target"];
serviceConfig = {
Type = "oneshot";
ExecStart = ["${cfg.package}/bin/hello"]; # For demo purposes
RemainAfterExit = true;
};
};
}
Then in interactive shell of the check’s driver (nix run -L .#checks.x86_64-linux.demo.driverInteractive
):
$ systemctl status soppps
● soppps.service
Loaded: loaded (/etc/systemd/system/soppps.service; enabled; preset: enabled)
Active: active (exited) since Mon 2024-03-18 20:48:14 UTC; 26s ago
Process: 657 ExecStart=/nix/store/7bl684y3qpxrv01ird085rpf5kl6rk6f-hello-2.12.1/bin/hello (code=exited, status=0/SUCCESS)
Main PID: 657 (code=exited, status=0/SUCCESS)
IP: 0B in, 0B out
CPU: 7ms
Mar 18 20:48:14 machine1 systemd[1]: Starting soppps.service...
Mar 18 20:48:14 machine1 hello[657]: Hello, world!
Mar 18 20:48:14 machine1 systemd[1]: Finished soppps.service.