Files
nix-config/modules/index.org
phundrak a2b21feacc refactor: change to litterate config
Configuration is now held by the `.org` files. All `.nix` files are
tangled from the org-mode files.
2026-10-04 22:34:28 +02:00

12 KiB
Raw Permalink Blame History

P’undrak’s Litterate Nix Config

Index

Hi, I’m P’undrak (pronounced PUN-drak, or more exactly ), also known as Lucien Cartier-Tilet. If you want to know more about me, you can head to my main website.

This website documents my entire Linux configuration, managed through NixOS. Most of my programs are configured through Nix, following the dendritic pattern.

To give you a tl;dr, this basically means that each aspect of my configuration (be it a concept or an application) tries to only have a single source of truth. But as you can see with my Emacs configuration, some aspects can be quite extensive and require several pages to be properly organised.

Note on What a Literate Configuration Is

As implied by the title of this website, my configuration is a litterate one. This means that every page of this website comes from an Emacs org-mode file, and the code you see as code blocks are the actual source of my configuration.

Org-mode offers to “tangle” these code blocks into full files, which can then be used as any other source file. If you visit this website’s repository, you will find the source org files alongside their resulting Nix file if any is generated by said file. For instance, this very page generates my modules/default.nix file, as we’ll see below.

This also implies heavy usage of the noweb syntax. If you encounter some code that looks <<like-this>>, org-mode will replace this snippet with another code snippet declared elsewhere in my configuration. If you see some code that looks <<like-this()>>, some generating code will run and replace this piece of text with the text generated. A quick example:

(defun hello ()
  <<generate-docstring()>>
  <<print-hello>>)

Will instead appear as

(defun hello ()
  <<generate-docstring()>>
  <<print-hello>>)

This is because I have the block of code below named generate-docstring which generates an output, which replaces its noweb tag. You can recognize noweb snippets generating code with the parenthesis. Often, such blocks aren’t visible in my HTML exports, but you can still see them if you open the actual org source file.

(concat "\""
        "Print \\\"Hello World!\\\" in the minibuffer."
        "\"")

On the other hand, noweb snippets without parenthesis simply replace the snippet with the equivalent named code block. For instance the one below is named print-hello and is placed as-is in the target source block.

(message "Hello World!")

Root Nix Configuration

As this configuration uses flake-parts and flake-file, I need a modules/default.nix which defines how to manage my config.

For the record, I use nh to compile my configuration and switch to newer generations of my OS and home. This allows me to simply run nh os switch to upgrade my system, or nh home switch to upgrade my home-manager configuration. This, therefore, requires to have flakes outputs in the form of nixosConfigurations.marpa (with marpa being the hostname of a machine) and homeConfigurations.phundrak or homeConfigurations.phundrak@marpa (with phundrak being the username of one of the users of a machine).

But first things first, let’s declare the closure that will encapsulate the rest of the file:

{
  inputs,
  lib,
  config,
  ...
}: {
  <<modules-imports>>

  <<options-home>>

  config = {
    <<flake-inputs>>
    <<dev-arch>>

    flake.lib = {
      <<lib-mkNixos>>
      <<lib-mkHome>>
      <<lib-mkPinetab>>
    };

    <<configs-decl>>
    <<devshell>>
  };
}

To get our configuration to work, we need to import the modules from flake-parts and flake-file.

imports = [
  inputs.flake-parts.flakeModules.modules
  inputs.flake-file.flakeModules.default
];

Now, we can declare an option to make homeConfigurations available as an output for our flakes.

options.flake.homeConfigurations = lib.mkOption {
  type = lib.types.lazyAttrsOf lib.types.raw;
  default = {};
};

Setting Things Up

We need to declare a few inputs for our flake, thanks to flake-file. The first one is flake-utils, which allows me to easily declare my development shell for this repository both for my x86-64 machines and my aarch64 tablet, a PineTab 2. Then, speaking of the PineTab, I need some specific nixpkgs input, to handle a bug in the compilation of the kernel, as well as the flake rockchip to support said kernel.

flake-file.inputs = {
  flake-utils.url = "github:numtide/flake-utils";
  nixpkgsPinetab2Kernel.url = "github:nixos/nixpkgs/e73de5be04e0eff4190a1432b946d469c794e7b4";
  rockchip = {
    url = "github:raboof/nixos-rockchip/pinetab-linux-7.0";
    inputs.utils.follows = "flake-utils";
    inputs.nixpkgsStable.follows = "nixpkgsStable";
    inputs.nixpkgsUnstable.follows = "nixpkgsPinetab2Kernel";
  };
};

As I said, I support two architectures for my development shell, so let’s declare them.

systems = ["x86_64-linux" "aarch64-linux"];

Now, we can declare some functions for our flake. These three functions’ role is to create the output of each machine and user as required. First, let’s create the function to get a machine’s configuration based on its name.

mkNixos = system: name: {
  ${name} = inputs.nixpkgs.lib.nixosSystem {
    modules = [
      config.flake.modules.nixos.${name}
      {nixpkgs.hostPlatform = lib.mkDefault system;}
    ];
  };
};

What this does is basically declare a Nix system named after the host’s name, created with its module (located in modules/hosts/) and the related nixpkgs with the appropriate architecture. For now, the only architecture used with this function is x86-64, but I’m not excluding the posibility to have other hosts using an ARM CPU.

mkHome is somewhat similar: it declares a home-manager module, importing the module related to the user and the machine it will be deployed on.

mkHome = system: userName: hostName: {
  "${userName}@${hostName}" = inputs.home-manager.lib.homeManagerConfiguration {
    pkgs = inputs.nixpkgs.legacyPackages.${system};
    extraSpecialArgs = {
      inherit inputs;
      bunBaseline = config.flake.packages.${system}.bun-baseline;
    };
    modules = [config.flake.modules.homeManager."${userName}-${hostName}"];
  };
};

You may notice the declaration of bunBaseline. This is because of my Thinkpad x220; the default binary distributed for Bun uses CPU instructions that are more recent than this laptop’s CPU, which results in fatal errors trying to run it. Therefore, bunBaseline is here to either compile Bun on my Thinkpad with the correct instruction set, or simply use a prepackaged Bun if the host supports it.

Lastly, I have a function dedicated to building NixOS on my PineTab 2.

mkPinetab = buildPlatform: variantModule: {
  pinetab2 = inputs.nixpkgs.lib.nixosSystem {
    system = "aarch64-linux";
    modules = [
      inputs.rockchip.nixosModules.sdImageRockchip
      inputs.rockchip.nixosModules.dtOverlayPCIeFix
      inputs.rockchip.nixosModules.noZFS
      config.flake.modules.nixos.pinetab2-base
      variantModule
      {
        rockchip.uBoot = inputs.rockchip.packages.${buildPlatform}.uBootPineTab2;
        boot.kernelPackages =
          inputs.rockchip.legacyPackages.${buildPlatform}.kernel_linux_7_0_pinetab_unstable;
        hardware.firmware = [inputs.rockchip.packages.aarch64-linux.bes2600];
        nixpkgs.config.allowUnfreePredicate = pkg:
          builtins.elem (inputs.nixpkgs.lib.getName pkg) ["bes2600-firmware"];
      }
    ];
  };
};

I won’t go into too much details here, but it mostly imports the modules required to run NixOS on the Pinetab as well as explicitly import the bes2600 firmware module to make Bluetooth and Wi-Fi available on the tablet.

Declaring the Actual Outputs

With all that being said, we can now actually declare our configurations. You can see below the table of hosts I have, with their CPU architecture, and which users are present on the system.

Host Architecture Users Comment
marpa x86_64-linux phundrak Main workstation
gampo x86_64-linux phundrak Thinkpad x220
tilo x86_64-linux phundrak Home Server
elcafe x86_64-linux phundrak, creug Server
NaroMk3 x86_64-linux phundrak Cloud Server
pinetab2 aarch64-linux phundrak PineTab 2 tablet
(config.flake.lib.mkNixos "x86_64-linux" "marpa")
(config.flake.lib.mkNixos "x86_64-linux" "gampo")
(config.flake.lib.mkNixos "x86_64-linux" "tilo")
(config.flake.lib.mkNixos "x86_64-linux" "elcafe")
(config.flake.lib.mkNixos "x86_64-linux" "NaroMk3")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "marpa")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "gampo")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "tilo")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "elcafe")
(config.flake.lib.mkHome "x86_64-linux" "creug" "elcafe")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "NaroMk3")
(config.flake.lib.mkHome "aarch64-linux" "phundrak" "pinetab2")

This translates into this Nix code.

flake.nixosConfigurations = lib.mkMerge [
  <<make-hosts()>>
  (config.flake.lib.mkPinetab "x86_64-linux" config.flake.modules.nixos.pinetab2-gnome)
];

flake.homeConfigurations = lib.mkMerge [
  <<make-home()>>
];

Development Shell

Lastly, here is the declaration of my development shell. It really is only useful if I’m on a new machine or a machine that is not quite up to date and misses some packages I rely on to work on my dotfiles. Namely, these are nh (which I mentioned above), Jujutsu, jj-cz (a Commitizen alternative for Jujutsu I’m working on), and Git itself as a fallback.

perSystem = {
  pkgs,
  system,
  ...
}: {
  formatter = pkgs.alejandra;
  devShells.default = pkgs.mkShell {
    buildInputs = [
      pkgs.nh
      pkgs.jujutsu
      pkgs.git
      inputs.jj-cz.packages.${system}.default
    ];
  };
};