Building a simple C daemon with Nix, adding it into an existing NixOS configuration without modifying nixpkgs, and then wiring the daemon up to systemd.

The daemon here isn't too noteworthy, it's a mashup of OSC and Lua as a simple remote control. I've called it oschkd. It's written in C and builds with make, provided that pkgconfig, liblo, and Lua 5.1 are available.

To build with Nix we can add a default.nix to the project root with a somewhat standard definition (adjust accordingly):

{ pkgs, lib, stdenv }:

stdenv.mkDerivation {
  meta = with pkgs.lib; {
    description = "OSC Hotkey Daemon";
    homepage = "https://demonastery.org";
    license = licenses.gpl3Plus;
  };

  pname = "oschkd";
  version = "git";

  buildInputs = with pkgs; [
    pkgconfig
    liblo
    lua51Packages.lua
  ];

  doCheck = true;

  src = ./.;

  installPhase = ''
    mkdir -p $out/bin

    cp    main          $out/bin/oschkd
  '';
}

This is enough of a definition to build the C application and link with the necessary libraries.

What wasn't obvious to me was how to actually build this with nix-build. Running what I hoped might work results in an error:

$ nix-build .
error: cannot evaluate a function that has an argument without a value ('pkgs')

It seems that most people call nix-build with a variation of this instead:

nix-build -E "with import <nixpkgs> {}; callPackage ./default.nix {}"

This should successfully build the application, leaving a result symlink in the current directory, and within it you'll find anything that was copied to $out within the installPhase section.

From here, adding it to your system is straightforward. I recommend adding an overlay, in your /etc/nixos/configuration.nix:

  nixpkgs.overlays = [
    (
      self: super:
      {
        oschkd = super.callPackage /home/path/to/src/oschkd {}; # path containing default.nix
      }
    )
  ];

With an overlay, you'll be able to access oschkd through pkgs as usual, for example to make it available globally to your system:

environment.systemPackages = with pkgs; [
  ...
  oschkd
];

And/or use it within other Nix expressions, for example adding a systemd user service:

systemd.user.services.oschkd = {
  enable = true;
  description = "OSC Hotkey Daemon";
  
  # Open up PATH to daemon for launching applications
  # NOTE "/bin" is appended to each
  path = [ "/home/user" "/run/current-system/sw" ];
  
  serviceConfig = {
    ExecStart = "${pkgs.oschkd}/bin/oschkd 9777 /home/user/.config/oschkd.lua";
    KillMode = "process";
    StandardOutput = "null"; # don't catch output from launched stuff
  };
  
  wantedBy = [ "graphical-session.target" ];
};

And that's it, just a nixos-rebuild switch away.