refactor: change to litterate config

Configuration is now held by the `.org` files. All `.nix` files are
tangled from the org-mode files.
This commit is contained in:
2026-10-04 22:34:28 +02:00
parent 5f4a7a4a42
commit a2b21feacc
13 changed files with 898 additions and 86 deletions
+343
View File
@@ -0,0 +1,343 @@
#+title: P’undrak’s Litterate Nix Config
#+setupfile: headers
#+property: header-args:emacs-lisp :lexical t :exports none :tangle no
* Index
Hi, I’m P’undrak (pronounced /PUN-drak/, or more exactly {{{phon(pʰynɖak̚)}}}),
also known as Lucien Cartier-Tilet. If you want to know more about me,
you can head to my [[https://phundrak.com/en][main website]].
This website documents my entire Linux configuration, managed through
NixOS. Most of my programs are configured through Nix, following the
[[https://github.com/Doc-Steve/dendritic-design-with-flake-parts][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
See [[https://labs.phundrak.com/phundrak/dotfiles/src/branch/master/LICENSE.org][the repository’s license file]].
** 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 [[https://orgmode.org/manual/Noweb-Reference-Syntax.html][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:
#+begin_src elisp
(defun hello ()
<<generate-docstring()>>
<<print-hello>>)
#+end_src
Will instead appear as
#+begin_src emacs-lisp :noweb yes
(defun hello ()
<<generate-docstring()>>
<<print-hello>>)
#+end_src
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.
#+name: generate-docstring
#+begin_src emacs-lisp
(concat "\""
"Print \\\"Hello World!\\\" in the minibuffer."
"\"")
#+end_src
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.
#+name: print-hello
#+begin_src emacs-lisp
(message "Hello World!")
#+end_src
** Root Nix Configuration
As this configuration uses [[https://flake.parts/][flake-parts]] and [[https://flake-file.denful.dev/][flake-file]], I need a
=modules/default.nix= which defines how to manage my config.
For the record, I use [[https://github.com/nix-community/nh][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
[[https://nix-community.github.io/home-manager/][home-manager]] configuration. This, therefore, requires to have [[https://nixos.wiki/wiki/flakes][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:
#+begin_src nix :tangle default.nix
{
inputs,
lib,
config,
...
}: {
<<modules-imports>>
<<options-home>>
config = {
<<flake-inputs>>
<<dev-arch>>
flake.lib = {
<<lib-mkNixos>>
<<lib-mkHome>>
<<lib-mkPinetab>>
};
<<configs-decl>>
<<devshell>>
};
}
#+end_src
To get our configuration to work, we need to import the modules from
flake-parts and flake-file.
#+name: modules-imports
#+begin_src nix
imports = [
inputs.flake-parts.flakeModules.modules
inputs.flake-file.flakeModules.default
];
#+end_src
Now, we can declare an option to make =homeConfigurations= available as
an output for our flakes.
#+name: options-home
#+begin_src nix
options.flake.homeConfigurations = lib.mkOption {
type = lib.types.lazyAttrsOf lib.types.raw;
default = {};
};
#+end_src
*** 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.
#+name: flake-inputs
#+begin_src nix
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";
};
};
#+end_src
As I said, I support two architectures for my development shell, so
let’s declare them.
#+name: dev-arch
#+begin_src nix
systems = ["x86_64-linux" "aarch64-linux"];
#+end_src
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.
#+name: lib-mkNixos
#+begin_src nix
mkNixos = system: name: {
${name} = inputs.nixpkgs.lib.nixosSystem {
modules = [
config.flake.modules.nixos.${name}
{nixpkgs.hostPlatform = lib.mkDefault system;}
];
};
};
#+end_src
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.
#+name: lib-mkHome
#+begin_src nix
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}"];
};
};
#+end_src
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.
#+name: lib-mkPinetab
#+begin_src nix
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"];
}
];
};
};
#+end_src
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.
#+name: table-hosts
| 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 |
#+name: make-hosts
#+begin_src emacs-lisp :exports none :var hosts=table-hosts :cache yes
(mapconcat
(lambda (line)
(let ((hostname (car line))
(arch (nth 1 line)))
(format "(config.flake.lib.mkNixos \"%s\" \"%s\")"
arch
hostname)))
(-filter (lambda (host) (not (string= "pinetab2" (car host))))
hosts)
"\n")
#+end_src
#+RESULTS[f5f8b3a89622e7526a83cbf722c4b7c77ff7bd86]: make-hosts
: (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")
#+name: make-home
#+begin_src emacs-lisp :exports none :var hosts=table-hosts :cache yes
(require 's)
(mapconcat
(lambda (line)
(let ((hostname (car line))
(arch (nth 1 line))
(users (mapcar #'s-trim (s-split "," (nth 2 line) t))))
(mapconcat (lambda (user)
(format "(config.flake.lib.mkHome \"%s\" \"%s\" \"%s\")"
arch user hostname))
users
"\n")))
hosts
"\n")
#+end_src
#+RESULTS[301fccb07591fffc8d7bdfd7d2958602150aa688]: make-home
: (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.
#+name: configs-decl
#+begin_src nix :noweb yes
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()>>
];
#+end_src
*** 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.
#+name: devshell
#+begin_src nix
perSystem = {
pkgs,
system,
...
}: {
formatter = pkgs.alejandra;
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.nh
pkgs.jujutsu
pkgs.git
inputs.jj-cz.packages.${system}.default
];
};
};
#+end_src