Installing a Custom Package With Nix
A (relatively) quick guide to creating a custom package on Nix and NixOS. Image from https://www.pexels.com/photo/blue-box-with-christmas-present-and-white-snowflake-6102171/.
Contents
Introduction ⚓
I’m writing this blog because I struggled with this seemingly simple problem for two days: how do I bundle up a custom package to install with Nix? Specifically: how do I download and “install” a binary file, plus a systemd service to manage it?
Context ⚓
One of the most essential programs for my home server is Duplicacy 🔗, an application that backs up all my data to Backblaze B2. While Duplicacy’s command-line tool is open source and available in Nixpkgs 🔗, its web client is neither of those. I also couldn’t find any package definitions created by other Duplicacy users, so I thought “whatever, I’ll just make my own!”
Duplicacy’s web client is a single binary file. You simply need to
download it 🔗, run chmod +x
to make it executable, then run it. To manage it like a service on my current Debian installation, I moved the file to /usr/local/bin, created a systemd service file, and pointed it to the executable. This works fine on Debian, but Nix is a whole other beast.
Solution ⚓
I’ll show what I did, then break it down later. There are two main files here:
package.nix
is where the package is defined.duplicacy-web.nix
is where the service is defined, and where the package gets added to the system.
First, the package definition:
{ pkgs, lib }:
pkgs.stdenv.mkDerivation rec {
pname = "duplicacy-web";
version = "1.7.2";
src = builtins.fetchurl {
url = "https://acrosync.com/duplicacy-web/duplicacy_web_linux_x64_${version}";
sha256 = "88383f7fea8462539cab7757dfa167bf42e37cbc19531b9de97373bc20efd317";
};
doCheck = false;
dontUnpack = true;
installPhase = ''
install -D $src $out/duplicacy-web
chmod a+x $out/duplicacy-web
'';
meta = with lib; {
homepage = "https://duplicacy.com";
description = "A new generation cloud backup tool";
platforms = platforms.linux ++ platforms.darwin;
#license = licenses.unfreeRedistributable; # TODO: For some reason Nix refuses to install unfree packages despite being configured otherwise. Disable for now.
};
}
Next, duplicacy-web.nix:
{ pkgs, config, lib, cfg, ... }:
let
duplicacy-web = pkgs.callPackage ./package.nix { inherit pkgs lib; };
in
rec {
environment.systemPackages = [
duplicacy-web
];
# Install systemd service
systemd.services."duplicacy-web" = {
enable = true;
wants = [ "network-online.target" ];
after = [ "syslog.target" "network-online.target" ];
description = "Start the Duplicacy backup service and web UI";
serviceConfig = {
Type = "simple";
ExecStart = ''${duplicacy-web}/duplicacy-web'';
Restart = "on-failure";
RestartSrc = 10;
KillMode = "process";
Environment = "HOME=${config.users.users.aires.home}";
};
};
}
Breakdown ⚓
package.nix - defining the package itself ⚓
Let’s start with package.nix
. We create this as a
derivation 🔗, which is sort of like a package blueprint. Once the blueprint is created, we can reference it in other Nix commands, for example, to install it to our system.
This derivation is simple: we name it “duplicacy-web,” pass a version number, then tell it how to fetch the file by providing a URL and SHA256 sum to verify the file’s integrity. Since this URL downloads a binary file, we don’t need to unpack anything, so we set dontUnpack = true;
. Otherwise, Nix will complain that it doesn’t know how to unpack the file.
The installPhase
tells Nix what to do with the file. All we need to do is tell Nix what to name the file and where to put it within the derivation, and then make it executable. These two lines do just that:
installPhase = ''
install -D $src $out/duplicacy-web
chmod a+x $out/duplicacy-web
'';
Lastly, we add some metadata about the package. This isn’t really necessary unless you’re looking to contribute this to nixpkgs. I ran into a problem here, though: duplicacy-web is proprietary software and has a nonfree license. Even though I supposedly enabled unfree licenses in my NixOS config 🔗, Nix still complained. So if you’re wondering why this is commented out, that’s why. Eventually I’ll figure it out.
meta = with lib; {
homepage = "https://duplicacy.com";
description = "A new generation cloud backup tool";
platforms = platforms.linux ++ platforms.darwin;
#license = licenses.unfreeRedistributable; # TODO: For some reason Nix refuses to install unfree packages despite being configured otherwise. Disable for now.
};
duplicacy-web.nix - pulling the package into a module ⚓
Here’s where we define what to do with the package. We’ll need to import this file into the rest of our configuration so it gets processed, but for now, let’s just focus on the contents.
First, we define duplicacy-web
as the result of the derivation by calling our packaged.nix
file. This step is what tripped me up the most, because I had absolutely no idea how to import or call a derivation from another file. I didn’t even know I needed to make a derivation to install a custom package. This single line is the result of many trial and error attempts. Don’t let its simplicity fool you!
duplicacy-web = pkgs.callPackage ../packages/duplicacy-web.nix { inherit pkgs lib; };
Next, we install duplicacy-web
like any other package, then create a systemd service for it. Note that this systemd service doesn’t start automatically: this is deliberate due to how my server is set up. My server has a separate encrypted data partition that I manually unlock on each reboot. Since Duplicacy backs up that partition, I don’t want it mounted before Duplicacy starts, otherwise Very Bad Things™ could happen. So I have a startup script that mounts the partition first, and then starts the service. If you want the service to start automatically, you can add wantedBy = ["multi-user.target"]
to start the service at boot, or wantedBy = ["default.target"]
to have it start on login. See
NixOS options 🔗 for details.
Also, the files that Duplicacy requires are stored in my user’s home directory, so within the service, I included an environment variable pointing to my home. If you want to reuse this service definition for yourself, make sure to replace ${config.users.users.aires.home}
with the path to the directory containing your .duplicacy-web
folder.
Importing the module into your system configuration ⚓
That’s pretty much it. Just import duplicacy-web.nix
in your main configuration file like so, and you’re done.
{ ... }: {
imports = [
./duplicacy-web.nix
];
# The rest of your configuration
}
When you run nixos-rebuild
, it’ll automatically build the duplicacy-web derivation and the systemd service, and include them both in your system. The service will have the right path to the derivation and will start just fine (assuming you start it yourself or via script, like I do).
Good luck on your NixOS journey!
Previous: "NixOS: What is it, how does it work, and should you use it?" | Next: "The Antidepressant That Changed My Life" |