Flakify your code as NixOS Packages and Modules
Tags: nix, nixos, sre, linux
Published: 2022-12-03 07:45:22

## Introduction
Recently I had to flakify a Django app so that it outputs a few packages and modules which can be installed and configured in the NixOS System. I needed to figure out how to get started. After a week of searches and reviewing other nix packages, I made it work.

Today, I'll try to explain whatever I have learned in this process. I may be wrong somewhere or maybe there's a better way, so feel free to correct me or share your knowledge in the comments section.

## Why Flakes?
When using normal Nix packages the derivation is fetched from the current nix-channel set in the system. So, we may sometimes arrive at a situation when we need to change the nix-channel for fetching a particular package. Now, several such packages from different channels quickly start building a mess. And making sure the nix-channels are in sync becomes hard. Flakes resolves this problem as we specify the nixpkgs version to use by providing its URL in the inputs.

In general, flakes make things a little more declarative and easy to understand and maintain. Also, the dependencies get pinned to a lock file, which makes sure that the flake output is consistently reproducible, the main moto of Nix.

## What are we going to do today?
Today we will be writing a script that prints a passed argument along with the current time after every 30 seconds.

### Setting up our project
First, let's initialize git.
```
git init
```

Now we will set up a basic flake by running the command 
```
nix flake init
```

It will create a basic flake.nix file.

![Flake.nix](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n7mm263oujy3yj78pedy.png)

Now let's create your bash script. First, we will create a folder called "src", the name can be anything but "src" clearly tells that it contains the source files.

![Bash script](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1t7tfoiinymd1b35k1n.png)

Now we will create the derivation for the package. I like to create a folder called "pkgs" and put the derivation there.

![Derivation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u44mlt9x7pej7ihne2fn.png)


Let us understand the derivation here.

```
pname = "nix-flake-demo";
```
We are defining the package name here. The package will be available to the path by this name.

```
outputs = [ "out" ];
```
We can have more outputs for different purposes, the out is a default one that links to the directory in the nix store for this derivation.

```
src = ../../src;
```
The location of the source files.

```
buildInputs = [ ];
```
We don't need anything else to build our derivation as this is a simple bash program. But for example, if we had a flask app, then we would have to pass python along with the flask package to the buildInputs.

```
installPhase = ''
    mkdir $out
    mkdir $out/bin
    cp -rv $src/* $out/bin
    chmod +x $out/bin/nix-flake-demo
'';
```

We create our output directory and a bin directory inside it. Then we copy our source files there and make the script executable.

> One thing to note here is that there should be an executable in the output bin directory of the name same as that of the derivation. If the script is named something else in the source just rename it in the installPhase.

There are several more Phases in mkDerivation. I am not mentioning the rest as I don't have much experience in them. 

You can read more about the different phases [here](https://gsc.io/70266391-48a6-49be-ab5d-acb5d7f17e76-nixpkgs/doc/nixpkgs-manual/html/sec-stdenv-phases.html).

Now we can edit the flake to output the derivation as a package.

![Adding package to flake](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k0nrx4hcfp3io8fcvp5k.png)

Let's understand the flake code till now.

```
inputs = {
  nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
  utils.url = "github:numtide/flake-utils";
};
```
We are providing the URL of the nixpkgs to be used in this flake. Also, we are providing flake-utils.

```
packages =
      let
        system = "x86_64-linux";
        pkgs = import nixpkgs { inherit system; };
      in
      utils.lib.eachDefaultSystemMap (system: {
        nix-flake-demo = pkgs.callPackage ./pkgs/nix-flake-demo { };
      });
```
We are outputting nix-flake-demo for all types of systems.

```
defaultPackage.x86_64-linux = self.packages.x86_64-linux.nix-flake-demo;
```
Then we specify the defaultPackage of this flake. So when we run the command `nix run`, it will run this package.

Let's test our flake till now. Don't forget to commit the changes first. Then run the command

```
nix run
```

![Nix run test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jj839lkgabn6zb3xant4.png)


> The script prints only the time as no argument was passed to it.


![Nix run with arguments](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9pz5bq9ln885jtq20zb7.png)


Now let's create the module which provides a systemd service to NixOS. I like to keep the code for that in the "modules" directory.


![NixOS Module](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jhgo9xzel88nu4l4byyj.png)



We provide a few options that can be configured in NixOS configuration. And if the enable option is set to true, we add the service to the system.
If you don't know how to add a flake to your NixOS configuration, let me know, and I will try to create a post on that.
I have a boilerplate to set up nix flake. Please visit this repo:

[nix-pkg-module-boilerplate](https://github.com/abhishekadhikari23/nix-pkg-module-boilerplate)

A fork and star will be highly appreciated.

Thanks for reading till the end. Hope I was able to teach something new. Please provide your feedback or request topics in the comment section.

With ❤️ by [Abhishek Adhikari](https://www.the-xpert.me/).