I have had NixOS deployed on all of my on-prem headless hardware for about 2 years now, and I still feel like I know nothing about it.
Deploying someone elses module or installing someone elses package on NixOS is easy, but actually developing on it has quite the steep learning curve. This post is not intended to replace the actual documentation; it’s just a polished version of my notes and a way for me to reinforce what I learned.
If you want to skip my long winded explanations, the complete derivation and service file can be found here
The App
The app I deployed is phin05’s discord-rich-presence-plex. It’s written in Python and does not have an entry in Nixpkgs. Sure, it has a Dockerfile and a compose sheet, but whats the fun in that?
Building the derivation
This derivation is very simple. Nix expressions are just outputs based on inputs. The derivation takes these inputs:
{
lib,
python3Packages,
python3,
fetchFromGitHub,
makeWrapper
}:
We then use the buildPythonApplication derivation wrapper included in the python3 input.
Note the rec keyword. This defines the set of options as a recursive set, meaning they can reference one another, such as how rev references version.
This then defines the name and version of our application and uses the fetchFromGitHub function defined as an input earlier to grab the source code.
python3Packages.buildPythonApplication rec {
pname = "discord-rich-presence-plex";
version = "2.16.0";
src = fetchFromGitHub {
owner = "phin05";
repo = "discord-rich-presence-plex";
rev = "v${version}";
sha256 = "sha256-e1r0w72IOEY5XsjANkAHbfPYEf1B8n6KYVLMWFSLs0g="; # use lib.fakeSha256 on first build to get the correct one.
Runtime and buildtime dependencies are defined within propogatedBuildInputs.
dependencies = with python3Packages; [
plexapi
requests
websocket-client
pyyaml
pillow
];
Buildtime dependencies are defined within nativeBuildInputs. Note that these are Nix tools for the Nix buildtime, not dependencies for the Python application.
nativeBuildInputs = [
makeWrapper
];
This project does not use setup-tools, so we will be skipping the build and testing process, as well as any dependency injection for those tests.
dontBuild = true;
format = "other";
dontUseSetuptoolsBuild = true;
dontUseSetuptoolsCheck = true;
Pretty self-explanatory. Builds the application and puts the appropriate files in the appropriate places.
installPhase = ''
runHook preInstall
mkdir -p $out/lib/discord-rich-presence-plex
cp -r * $out/lib/discord-rich-presence-plex/
mkdir -p $out/bin
makeWrapper \
${lib.getExe python3}\
$out/bin/discord-rich-presence-plex \
--add-flags "$out/lib/discord-rich-presence-plex/main.py" \
--prefix PYTHONPATH : "$out/lib/discord-rich-presence-plex:$PYTHONPATH" \
--set DRPP_NO_PIP_INSTALL "true" # application specific, removes call to pip in script
runHook postInstall
'';
Meta information for the application.
meta = {
homepage = "https://github.com/phin05/discord-rich-presence-plex";
license = lib.licenses.gpl3;
description = "Displays your Plex status on Discord using Rich Presence";
maintainers = with lib.maintainers; [ hogcycle ];
mainProgram = "discord-rich-presence-plex";
changelog = "https://github.com/phin05/discord-rich-presence-plex/releases/tag/v${version}";
};
}
With our derivation now complete, we can export it as a package and then import that into systemPackages on our configuration.nix.
environment.systemPackages = let discord-rich-presence-plex = pkgs.callPackage ./discord-rich-presence-plex.nix {};
in with pkgs; [
discord-rich-presense-plex
# more system packages
];
Next I will be writing a systemd service to manage this application as well as a Nix module for managing it cleanly within my configuration, but that will come as a seperate post.