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-06 12:40:58 +02:00
parent 5f4a7a4a42
commit ba018ef841
62 changed files with 2374 additions and 519 deletions
+21 -39
View File
@@ -1,44 +1,26 @@
{
flake.modules.nixos.hardened = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.boot.kernel;
in {
options.mySystem.boot.kernel.hardened = mkEnableOption "Enables hardened Linux kernel";
config.boot = {
kernelModules = lists.optional cfg.hardened "tcp_bbr";
kernel.sysctl = mkIf cfg.hardened {
"kernel.sysrq" = 0; # Disable magic SysRq key
# Ignore ICMP broadcasts to avoid participating in Smurf attacks
"net.ipv4.icmp_echo_ignore_broadcasts" = 1;
# Ignore bad ICMP errors
"net.ipv4.icmp_ignore_bogus_error_responses" = 1;
# SYN flood protection
"net.ipv4.tcp_syncookies" = 1;
# Do not accept ICMP redirects (prevent MITM attacks)
"net.ipv4.conf.all.accept_redirects" = 0;
"net.ipv4.conf.default_accept_redirects" = 0;
"net.ipv4.conf.all.secure_redirects" = 0;
"net.ipv4.conf.default.secure_redirects" = 0;
"net.ipv6.conf.all.accept_redirects" = 0;
"net.ipv6.conf.default.accept_redirects" = 0;
# Do not send ICMP redirects (we are not a router)
"net.ipv4.conf.all.send_redirects" = 0;
# Do not accept IP source route packets (we are not a router)
"net.ipv4.conf.all.accept_source_route" = 0;
"net.ipv6.conf.all.accept_source_route" = 0;
# Protect against tcp time-wait assassination hazards
"net.ipv4.tcp_rfc1337" = 1;
# Latency reduction
"net.ipv4.tcp_fastopen" = 3;
# Bufferfloat mitigations
"net.ipv4.tcp_congestion_control" = "bbr";
"net.core.default_qdisc" = "cake";
};
boot = {
kernelModules = ["tcp_bbr"];
kernel.sysctl = {
"kernel.sysrq" = 0;
"net.ipv4.icmp_echo_ignore_broadcasts" = 1;
"net.ipv4.icmp_ignore_bogus_error_responses" = 1;
"net.ipv4.conf.all.accept_redirects" = 0;
"net.ipv4.conf.default_accept_redirects" = 0;
"net.ipv4.conf.all.secure_redirects" = 0;
"net.ipv4.conf.default.secure_redirects" = 0;
"net.ipv6.conf.all.accept_redirects" = 0;
"net.ipv6.conf.default.accept_redirects" = 0;
"net.ipv4.conf.all.send_redirects" = 0;
"net.ipv4.conf.all.accept_source_route" = 0;
"net.ipv6.conf.all.accept_source_route" = 0;
"net.ipv4.tcp_syncookies" = 1;
"net.ipv4.tcp_rfc1337" = 1;
"net.ipv4.tcp_congestion_control" = "bbr";
"net.core.default_qdisc" = "cake";
"net.ipv4.tcp_fastopen" = 3;
};
};
};
}
+116
View File
@@ -0,0 +1,116 @@
#+title: Kernel Hardening
#+setupfile: ../headers
* Kernel Hardening
Some of my machines are exposed to the Internet, and therefore get
their kernel hardened. First, let me declare the Nix file’s skeleton,
with the =nixos.hardened= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.hardened = {
boot = {
<<kernel-modules>>
<<kernel-options>>
};
};
}
#+end_src
** Kernel Modules
The very first thing to do is to load the =tcp_bbr= kernel module. It
increases the connection speed of the system with a better congestion
control. It is particularly interesting for my servers, as they may
have to deal with high traffic if a crawler ever decides to explore
all webpages offered by some websites I host. See [[https://www.cyberciti.biz/cloud-computing/increase-your-linux-server-internet-speed-with-tcp-bbr-congestion-control/][this article]] by
Nixcraft for more details.
#+name: kernel-modules
#+begin_src nix
kernelModules = ["tcp_bbr"];
#+end_src
** Kernel Options
Next are a series of kernel options.
#+name: kernel-options
#+begin_src nix
kernel.sysctl = {
<<sysrq-key>>
<<icmp>>
<<icmp-no-accept-redirects>>
<<icmp-no-send-redirects>>
<<ip-source-route-packets>>
<<syn>>
<<tcp-time-wait>>
<<bufferfloat>>
<<latency>>
};
#+end_src
First, we’ll disable the magic SysRq key. Not that I expect anyone to
have physical access to my servers, but it is a really powerful tool
that I’d rather have off.
#+name: sysrq-key
#+begin_src nix
"kernel.sysrq" = 0;
#+end_src
Next, we’ll ignore ICMP broadcasts to avoid participating in Smurf
attacks, and we’ll also ignore ICMP errors.
#+name: icmp
#+begin_src nix
"net.ipv4.icmp_echo_ignore_broadcasts" = 1;
"net.ipv4.icmp_ignore_bogus_error_responses" = 1;
#+end_src
Speaking of ICMP, we won’t accept ICMP redirects to prevent some MITM
attacks.
#+name: icmp-no-accept-redirects
#+begin_src nix
"net.ipv4.conf.all.accept_redirects" = 0;
"net.ipv4.conf.default_accept_redirects" = 0;
"net.ipv4.conf.all.secure_redirects" = 0;
"net.ipv4.conf.default.secure_redirects" = 0;
"net.ipv6.conf.all.accept_redirects" = 0;
"net.ipv6.conf.default.accept_redirects" = 0;
#+end_src
And we won’t send ICMP redirects (we’re not a router).
#+name: icmp-no-send-redirects
#+begin_src nix
"net.ipv4.conf.all.send_redirects" = 0;
#+end_src
We’re stil not a router, so we’ll refuse IP source route packets, both
on IPV4 and IPV6.
#+name: ip-source-route-packets
#+begin_src nix
"net.ipv4.conf.all.accept_source_route" = 0;
"net.ipv6.conf.all.accept_source_route" = 0;
#+end_src
Now, let’s get some SYN flood protection.
#+name: syn
#+begin_src nix
"net.ipv4.tcp_syncookies" = 1;
#+end_src
And protection against TCP time-wait assassination hazards.
#+name: tcp-time-wait
#+begin_src nix
"net.ipv4.tcp_rfc1337" = 1;
#+end_src
We will also mitigate bufferfloat, including with BBR (hey, we enabled that above!)
#+name: bufferfloat
#+begin_src nix
"net.ipv4.tcp_congestion_control" = "bbr";
"net.core.default_qdisc" = "cake";
#+end_src
And lastly, we’ll reduce latency on IPV4.
#+name: latency
#+begin_src nix
"net.ipv4.tcp_fastopen" = 3;
#+end_src
And we should be good to go!
+2 -11
View File
@@ -1,6 +1,4 @@
{config, ...}: let
flakeModules = config.flake.modules;
in {
{
flake.modules.nixos.kernel = {
pkgs,
config,
@@ -10,8 +8,6 @@ in {
with lib; let
cfg = config.mySystem.boot.kernel;
in {
imports = [flakeModules.nixos.amdgpu];
options.mySystem.boot.kernel = {
package = mkOption {
type = types.raw;
@@ -35,13 +31,8 @@ in {
'';
};
};
config.boot = {
initrd.kernelModules = lib.lists.singleton (
if config.mySystem.hardware.amdgpu.enable
then "amdgpu"
else "i915"
);
initrd.kernelModules = ["i915"];
extraModprobeConfig =
strings.concatLines
([cfg.extraModprobeConfig]
+136
View File
@@ -0,0 +1,136 @@
#+title: Kernel Configuration
#+setupfile: ../headers
* Kernel Configuration
This module centralises everything related to the kernel: which
package to boot, which extra modules to load, which KVM module the
CPU needs, and a couple of modprobe quirks for specific devices.
Here’s the skeleton of the =nixos.kernel= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.kernel = {
pkgs,
config,
lib,
...
}:
with lib; let
cfg = config.mySystem.boot.kernel;
in {
<<options>>
<<config>>
};
}
#+end_src
** Declaring the Options
#+name: options
#+begin_src nix
options.mySystem.boot.kernel = {
<<opt-package>>
<<opt-modules>>
<<opt-cpu-vendor>>
<<opt-v4l2loopback>>
<<opt-extra-modprobe>>
};
#+end_src
*** Kernel Package
=package= picks which kernel to boot. I default to the Zen kernel for
its desktop-tuned scheduler, but a host can override it with a
hardened or hardware-specific kernel instead.
#+name: opt-package
#+begin_src nix
package = mkOption {
type = types.raw;
default = pkgs.linuxPackages_zen;
};
#+end_src
*** Extra Kernel Modules
=modules= lists any extra kernel modules a host needs beyond the ones
this module already adds on its own.
#+name: opt-modules
#+begin_src nix
modules = mkOption {
type = types.listOf types.str;
default = [];
};
#+end_src
*** CPU Vendor
=cpuVendor= tells the module which KVM module to load, since Intel and
AMD CPUs each need their own.
#+name: opt-cpu-vendor
#+begin_src nix
cpuVendor = mkOption {
description = "Intel or AMD?";
type = types.enum ["intel" "amd"];
default = "amd";
};
#+end_src
*** Virtual Webcam
=v4l2loopback.enable= turns on a virtual video device. I feed it a
video source, and OBS Studio picks it up as if it were a webcam.
#+name: opt-v4l2loopback
#+begin_src nix
v4l2loopback.enable = mkEnableOption "Enables v4l2loopback kernel module";
#+end_src
*** Extra Modprobe Configuration
=extraModprobeConfig= lets a host inject raw =modprobe.d= lines. The
example below fixes a USB sound card that otherwise misconfigures
itself on boot.
#+name: opt-extra-modprobe
#+begin_src nix
extraModprobeConfig = mkOption {
type = types.lines;
default = "";
example = ''
options snd_usb_audio vid=0x1235 pid=0x8212 device_setup=1
'';
};
#+end_src
** Wiring It All Up
#+name: config
#+begin_src nix
config.boot = {
<<initrd-driver>>
<<extra-modprobe-lines>>
<<kernel-packages-and-modules>>
};
#+end_src
*** Choosing the Initrd Driver
Most of my machines have Intel graphics, so this module loads =i915=
early, in the initrd, to keep the Plymouth splash screen from flashing
or glitching before the proper driver takes over. Machines with an AMD
GPU instead load =amdgpu= early; the [[file:../hardware/amdgpu.org][AMD GPU module]] handles that
itself, so this module doesn’t need to know or care which GPU a host
actually has.
#+name: initrd-driver
#+begin_src nix
initrd.kernelModules = ["i915"];
#+end_src
*** Assembling Modprobe Configuration
This concatenates whatever a host set through =cfg.extraModprobeConfig=
with the v4l2loopback quirk line whenever =v4l2loopback.enable= is on.
#+name: extra-modprobe-lines
#+begin_src nix
extraModprobeConfig =
strings.concatLines
([cfg.extraModprobeConfig]
++ lists.optional cfg.v4l2loopback.enable ''options v4l2loopback exclusive_caps=1 devices=1 video_nr=0 card_label="OBS Studio"'');
#+end_src
*** Kernel Package and Extra Modules
Finally, =kernelPackages= and =kernelModules= wire the chosen package
and modules through, appending the vendor-specific KVM module.
#+name: kernel-packages-and-modules
#+begin_src nix
kernelPackages = cfg.package;
kernelModules = cfg.modules ++ ["kvm-${cfg.cpuVendor}"];
#+end_src
+16 -30
View File
@@ -7,39 +7,25 @@
with lib; let
cfg = config.mySystem.boot;
in {
options.mySystem.boot = {
systemd-boot = mkOption {
type = types.bool;
default = !cfg.grub.enable;
description = "Does the system use systemd-boot?";
};
grub = {
enable = mkEnableOption "Does the system use GRUB? (Disables systemd-boot)";
device = mkOption {
type = types.path;
description = "The GRUB device";
default = "";
};
};
zfs = {
enable = mkEnableOption "Enables ZFS";
pools = mkOption {
type = types.listOf types.str;
default = [];
};
options.mySystem.boot.systemd-boot = mkOption {
type = types.bool;
default = !cfg.grub.enable;
description = "Does the system use systemd-boot?";
};
options.mySystem.boot.grub = {
enable = mkEnableOption "Does the system use GRUB? (Disables systemd-boot)";
device = mkOption {
type = types.path;
description = "The GRUB device";
};
};
config.boot = {
loader = {
systemd-boot.enable = cfg.systemd-boot;
efi.canTouchEfiVariables = cfg.systemd-boot;
grub = mkIf cfg.grub.enable {
inherit (cfg.grub) enable device;
};
};
supportedFilesystems = mkIf cfg.zfs.enable ["zfs"];
zfs.extraPools = mkIf cfg.zfs.enable cfg.zfs.pools;
config.boot.loader = {
systemd-boot.enable = cfg.systemd-boot;
efi.canTouchEfiVariables = cfg.systemd-boot;
};
config.boot.loader.grub = mkIf cfg.grub.enable {
inherit (cfg.grub) enable device;
};
};
}
+73
View File
@@ -0,0 +1,73 @@
#+title: Bootloaders and Filesystems
#+setupfile: ../headers
* Bootloaders and Filesystems
This page sets up the bootloader of my machines. Whilst I generally
prefer to use [[https://systemd.io/BOOT/][systemd-boot]], some of my VPS use GRUB. All that gets
exposed with my module =nixos.loader=.
#+begin_src nix :tangle yes
{
flake.modules.nixos.loader = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.boot;
in {
<<systemd-boot-options>>
<<grub-options>>
<<systemd-boot-config>>
<<grub-config>>
};
}
#+end_src
By default, I want to use systemd-boot on my machines, but I need it
to be disabled whenever I use something else. For now, this “something
else” is only GRUB, but I’m not excluding using something else on yet
another machine such as [[https://www.rodsbooks.com/refind/][rEFInd]]. To ensure a single source of truth
regarding whether systemd-boot is to be used, I have an option for
that.
#+name: systemd-boot-options
#+begin_src nix
options.mySystem.boot.systemd-boot = mkOption {
type = types.bool;
default = !cfg.grub.enable;
description = "Does the system use systemd-boot?";
};
#+end_src
I can now set some options depending on that, such as whether to
enable systemd-boot itself (duh), and whether the installation process
can touch my EFI variables.
#+name: systemd-boot-config
#+begin_src nix
config.boot.loader = {
systemd-boot.enable = cfg.systemd-boot;
efi.canTouchEfiVariables = cfg.systemd-boot;
};
#+end_src
But, I have a VPS that requires me to use GRUB. For this, I also have
an option to enable it.
#+name: grub-options
#+begin_src nix
options.mySystem.boot.grub = {
enable = mkEnableOption "Does the system use GRUB? (Disables systemd-boot)";
device = mkOption {
type = types.path;
description = "The GRUB device";
};
};
#+end_src
I can no pass these options to the boot configuration of my machine.
#+name: grub-config
#+begin_src nix
config.boot.loader.grub = mkIf cfg.grub.enable {
inherit (cfg.grub) enable device;
};
#+end_src
+20 -30
View File
@@ -1,35 +1,25 @@
{
flake.modules.nixos.plymouth = {
pkgs,
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.boot.plymouth;
in {
options.mySystem.boot.plymouth.enable = mkEnableOption "Enables Plymouth at system boot";
config.boot = mkIf cfg.enable {
plymouth = {
inherit (cfg) enable;
theme = "circle_hud";
themePackages = with pkgs; [
(adi1090x-plymouth-themes.override {
selected_themes = ["circle_hud"];
})
];
};
consoleLogLevel = 3;
initrd.verbose = false;
kernelParams = [
"quiet"
"splash"
"boot.shell_on_fail"
"udev.log_priority=3"
"rd.systemd.show_status=auto"
flake.modules.nixos.plymouth = {pkgs, ...}: {
boot = {
plymouth = {
enable = true;
theme = "circle_hud";
themePackages = with pkgs; [
(adi1090x-plymouth-themes.override {
selected_themes = ["circle_hud"];
})
];
# Loader appears only if a key is pressed
loader.timeout = 0;
};
kernelParams = [
"quiet"
"splash"
"boot.shell_on_fail"
"udev.log_level=3"
"rd.systemd.show_status=auto"
];
consoleLogLevel = 3;
initrd.verbose = false;
loader.timeout = 0;
};
};
}
+99
View File
@@ -0,0 +1,99 @@
#+title: Plymouth
#+setupfile: ../headers
* Plymouth
Plymouth is a utility which shows an animation at startup or when
powering off a device, instead of showing only terminal things. My
Plymouth settings are set in the =nixos.plymouth= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.plymouth = {pkgs, ...}: {
boot = {
<<plymouth>>
<<kernel-parameters>>
<<quiet-console>>
<<no-menu>>
};
};
}
#+end_src
** Quieting Down the Console
First, I need to set a few kernel parameters to make displaying
Plymouth possible:
#+name: kernel-parameters-list
- =quiet= :: silences the logs in the terminal
- =splash= :: show the splash screen (in our case, Plymouth)
- =boot.shell_on_fail= :: sets =allowShell=1=, which permits the =fail()=
handler to drop an interactive rescue shell if stage-1 boot fails
- =udev.log_level=3= :: sets udev’s log level to =err=
- =rd.systemd.show_status=auto= :: suppress systemd status messages in
initrd unless boot is significantly delayed
#+name: kernel-params
#+begin_src emacs-lisp :var params=kernel-parameters-list :cache yes
(mapconcat (lambda (param)
(format "\"%s\""
(s-chop-suffix "=" (s-chop-prefix "=" (car (s-split " ::" param))))))
params
"\n")
#+end_src
#+RESULTS[7a824c095f2934792b4a3749e56091a8603aaacf]: kernel-params
: "quiet"
: "splash"
: "boot.shell_on_fail"
: "udev.log_level=3"
: "rd.systemd.show_status=auto"
This translates into:
#+name: kernel-parameters
#+begin_src nix :noweb yes
kernelParams = [
<<kernel-params()>>
];
#+end_src
To further decrease the verbosity of the booting screen, we can
deactivate initrd’s verbosity and lower the console’s log level to
=err=.
#+name: quiet-console
#+begin_src nix
consoleLogLevel = 3;
initrd.verbose = false;
#+end_src
And we’ll show Plymouth immediately, skipping the bootloader’s menu.
We can hold a key to force displaying the menu, though.
#+name: no-menu
#+begin_src nix
loader.timeout = 0;
#+end_src
** Configuring Plymouth
Now, we can configure Plymouth proper.
#+name: plymouth
#+begin_src nix
plymouth = {
enable = true;
<<plymouth-theme>>
};
#+end_src
The only significant configuration I want to change in Plymouth is the
animation. I really like how it looks. You can find a preview of it in
[[https://github.com/adi1090x/plymouth-themes#previews][adi1090x's repository]], under the first pack. For this, I need to set
a theme package and the theme name.
#+name: plymouth-theme
#+begin_src nix
theme = "circle_hud";
themePackages = with pkgs; [
(adi1090x-plymouth-themes.override {
selected_themes = ["circle_hud"];
})
];
#+end_src
And we’re done!
+21
View File
@@ -0,0 +1,21 @@
{
flake.modules.nixos.zfs = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.boot;
in {
options.mySystem.boot.zfs = {
pools = mkOption {
type = types.listOf types.str;
default = [];
};
forceImportRoot = mkEnableOption "Force-import the ZFS root pool at boot, even if it looks already imported elsewhere";
};
config.boot.supportedFilesystems = ["zfs"];
config.boot.zfs.extraPools = cfg.zfs.pools;
config.boot.zfs.forceImportRoot = cfg.zfs.forceImportRoot;
};
}
+62
View File
@@ -0,0 +1,62 @@
#+title: ZFS Support
#+setupfile: ../headers
* ZFS Support
Enabling ZFS is quite straightforward on my machines. In my module
=nixos.zfs=, I expose two options: which ZFS pools to import, and
whether to force-import the root pool. But first, here’s the skeleton
of my module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.zfs = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.boot;
in {
<<options>>
<<enable>>
<<pools>>
<<force-import-root>>
};
}
#+end_src
The main option is the list of pools, which I pass directly to the
standard NixOS configuration.
#+name: options
#+begin_src nix
options.mySystem.boot.zfs = {
pools = mkOption {
type = types.listOf types.str;
default = [];
};
forceImportRoot = mkEnableOption "Force-import the ZFS root pool at boot, even if it looks already imported elsewhere";
};
#+end_src
Now, I can enable ZFS on the system importing the current module.
#+name: enable
#+begin_src nix
config.boot.supportedFilesystems = ["zfs"];
#+end_src
And lastly, passing the list of pools to the standard NixOS option is
quite simple.
#+name: pools
#+begin_src nix
config.boot.zfs.extraPools = cfg.zfs.pools;
#+end_src
Force-importing the root pool can paper over a stale or mismatched
host ID, but it also risks mounting a pool that’s already imported
elsewhere and corrupting it, so NixOS is moving to disable it by
default. I’d rather keep that safety and only turn it back on for the
rare host (or the rare boot) that actually needs it.
#+name: force-import-root
#+begin_src nix
config.boot.zfs.forceImportRoot = cfg.zfs.forceImportRoot;
#+end_src