Configuration is now held by the `.org` files. All `.nix` files are tangled from the org-mode files.
12 KiB
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.
License
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
];
};
};