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
+7 -12
View File
@@ -25,7 +25,6 @@
inputs.nixpkgsUnstable.follows = "nixpkgsPinetab2Kernel";
};
};
systems = ["x86_64-linux" "aarch64-linux"];
flake.lib = {
@@ -37,7 +36,6 @@
];
};
};
mkHome = system: userName: hostName: {
"${userName}@${hostName}" = inputs.home-manager.lib.homeManagerConfiguration {
pkgs = inputs.nixpkgs.legacyPackages.${system};
@@ -48,7 +46,6 @@
modules = [config.flake.modules.homeManager."${userName}-${hostName}"];
};
};
mkPinetab = buildPlatform: variantModule: {
pinetab2 = inputs.nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
@@ -72,24 +69,22 @@
};
flake.nixosConfigurations = lib.mkMerge [
(config.flake.lib.mkNixos "x86_64-linux" "elcafe")
(config.flake.lib.mkNixos "x86_64-linux" "gampo")
(config.flake.lib.mkNixos "x86_64-linux" "marpa")
(config.flake.lib.mkNixos "x86_64-linux" "NaroMk3")
(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.mkPinetab "x86_64-linux" config.flake.modules.nixos.pinetab2-gnome)
];
flake.homeConfigurations = lib.mkMerge [
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "elcafe")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "gampo")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "marpa")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "NaroMk3")
(config.flake.lib.mkHome "x86_64-linux" "phundrak" "pinetab2")
(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")
];
perSystem = {
pkgs,
system,
+7 -1
View File
@@ -15,6 +15,12 @@
example = "kde";
description = "Which DE to enable";
};
videoDrivers = mkOption {
type = types.listOf types.str;
default = [];
example = ["amdgpu"];
description = "Extra X11 video drivers to load";
};
};
config.services = mkIf cfg.enable {
displayManager = {
@@ -36,7 +42,7 @@
xserver = {
inherit (cfg) enable;
videoDrivers = lists.optional config.mySystem.hardware.amdgpu.enable "amdgpu";
videoDrivers = cfg.videoDrivers;
xkb = {
layout = "fr";
variant = "bepo_afnor";
+5 -8
View File
@@ -8,15 +8,12 @@
with lib; let
cfg = config.mySystem.dev.qemu;
in {
options.mySystem.dev.qemu = {
enable = mkEnableOption "Enable QEMU";
users = mkOption {
type = types.listOf types.str;
default = ["phundrak"];
example = ["user1" "user2"];
};
options.mySystem.dev.qemu.users = mkOption {
type = types.listOf types.str;
default = ["phundrak"];
example = ["user1" "user2"];
};
config = mkIf cfg.enable {
config = {
programs.virt-manager.enable = true;
users.groups.libvirtd.members = cfg.users;
virtualisation = {
+99
View File
@@ -0,0 +1,99 @@
#+title: QEMU
#+setupfile: ../headers
* QEMU
On machines where I run virtual machines, I want =virt-manager=,
=libvirtd=, and a few supporting pieces set up together. Here’s the
skeleton of the =nixos.qemu= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.qemu = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.mySystem.dev.qemu;
in {
<<options>>
config = {
<<virt-manager>>
<<libvirtd-group>>
<<virtualisation>>
<<packages>>
<<firmware-tmpfiles>>
<<binfmt>>
};
};
}
#+end_src
** Declaring the Options
=users= lists which users get added to the =libvirtd= group, so they can
manage VMs without needing root. By default, I add myself to this
group.
#+name: options
#+begin_src nix
options.mySystem.dev.qemu.users = mkOption {
type = types.listOf types.str;
default = ["phundrak"];
example = ["user1" "user2"];
};
#+end_src
This option is then passed onto the standard NixOS option.
#+name: libvirtd-group
#+begin_src nix
users.groups.libvirtd.members = cfg.users;
#+end_src
** virt-manager
This pulls in the GUI I actually use to create and manage VMs.
#+name: virt-manager
#+begin_src nix
programs.virt-manager.enable = true;
#+end_src
** Virtualisation Backend
=libvirtd= is the daemon that actually runs and manages the VMs. SPICE
USB redirection lets me pass a USB device straight through to a guest
without unplugging it from the host first.
#+name: virtualisation
#+begin_src nix
virtualisation = {
libvirtd.enable = true;
spiceUSBRedirection.enable = true;
};
#+end_src
** Extra Packages
Besides =qemu= itself, =quickemu= lets me spin up a VM from a template in
one command, and =swtpm= provides the software TPM that guests like
Windows 11 insist on.
#+name: packages
#+begin_src nix
environment.systemPackages = with pkgs; [
qemu
quickemu
swtpm
];
#+end_src
** Firmware Lookup Path
=virt-manager= and friends expect to find UEFI firmware images under
=/var/lib/qemu/firmware=, so I symlink QEMU’s own copy there instead of
making every tool aware of the Nix store path.
#+name: firmware-tmpfiles
#+begin_src nix
systemd.tmpfiles.rules = ["L+ /var/lib/qemu/firmware - - - - ${pkgs.qemu}/share/qemu/firmware"];
#+end_src
** ARM Emulation
This lets me run (and build) =aarch64-linux= binaries transparently
through QEMU’s user-mode emulation, which comes in handy when working
on the PineTab2 without needing actual ARM hardware on hand.
#+name: binfmt
#+begin_src nix
boot.binfmt.emulatedSystems = ["aarch64-linux"];
#+end_src
+45 -59
View File
@@ -1,64 +1,50 @@
{
flake.modules.nixos.amdgpu = {
pkgs,
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.hardware.amdgpu;
in {
options.mySystem.hardware.amdgpu.enable = mkEnableOption "Enables an AMD GPU configuration";
config = mkIf cfg.enable {
hardware = {
graphics = {
enable = true;
enable32Bit = true;
extraPackages = with pkgs; [
mesa # Mesa drivers for AMD GPUs
rocmPackages.clr # common language runtime for ROCm
rocmPackages.clr.icd # ROCm ICD for OpenCL
rocmPackages.rocblas # ROCm BLAS library
rocmPackages.hipblas #
rocmPackages.rpp # High-performance computer vision library
nvtopPackages.amd # GPU utilization monitoring
];
};
amdgpu = {
initrd.enable = true;
opencl.enable = true;
};
};
environment.systemPackages = with pkgs; [
clinfo
amdgpu_top
nvtopPackages.amd
];
systemd = {
packages = with pkgs; [lact];
services.lactd.wantedBy = ["multi-user.target"];
tmpfiles.rules = let
rocmEnv = pkgs.symlinkJoin {
name = "rocm-combined";
paths = with pkgs.rocmPackages; [
clr
clr.icd
rocblas
hipblas
rpp
];
};
in [
"L+ /opt/rocm - - - - ${rocmEnv}"
flake.modules.nixos.amdgpu = {pkgs, ...}: {
hardware.graphics = {
enable = true;
enable32Bit = true;
};
hardware.amdgpu = {
initrd.enable = true;
opencl.enable = true;
};
hardware.graphics.extraPackages = with pkgs; [
mesa
rocmPackages.clr
rocmPackages.clr.icd
rocmPackages.rocblas
rocmPackages.hipblas
rocmPackages.rpp
nvtopPackages.amd
];
environment.systemPackages = with pkgs; [
clinfo
amdgpu_top
nvtopPackages.amd
];
systemd = {
packages = with pkgs; [lact];
services.lactd.wantedBy = ["multi-user.target"];
tmpfiles.rules = let
rocmEnv = pkgs.symlinkJoin {
name = "rocm-combined";
paths = with pkgs.rocmPackages; [
clr
clr.icd
rocblas
hipblas
rpp
];
};
environment.variables = {
ROCM_PATH = "/opt/rocm"; # Set ROCm path
HIP_VISIBLE_DEVICES = "1"; # Use only the eGPU (ID 1)
ROCM_VISIBLE_DEVICES = "1"; # Optional: ROCm equivalent for visibility
# LD_LIBRARY_PATH = "/opt/rocm/lib"; # Add ROCm libraries
HSA_OVERRIDE_GFX_VERSION = "10.3.0"; # Set GFX version override
};
};
in [
"L+ /opt/rocm - - - - ${rocmEnv}"
];
};
environment.variables = {
ROCM_PATH = "/opt/rocm";
HIP_VISIBLE_DEVICES = "1";
ROCM_VISIBLE_DEVICES = "1";
HSA_OVERRIDE_GFX_VERSION = "10.3.0";
};
};
}
+147
View File
@@ -0,0 +1,147 @@
#+title: AMD GPU support
#+setupfile: ../headers
#+property: header-args:emacs-lisp :lexical t :exports none :tangle no
* AMD GPU support
Only one of my machines has an AMD GPU, but it does require some
tweaking. First, here’s the skeleton of the =nixos.amdgpu= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.amdgpu = {pkgs, ...}: {
<<enable-graphics>>
<<enable-hardware>>
<<hardware-extra-packages>>
<<system-packages>>
<<lact>>
<<environment-variables>>
};
}
#+end_src
** Enable the Hardware
The first thing is to enable graphics in NixOS. We’ll enable 32-bit
support while we’re at it.
#+name: enable-graphics
#+begin_src nix
hardware.graphics = {
enable = true;
enable32Bit = true;
};
#+end_src
We can now enable the hardware proper, including initrd and OpenCL
support for the GPU. =initrd.enable= already makes NixOS load =amdgpu= as
early as stage 1 on its own, which is why the kernel module (see
[[file:../boot/kernel.org][Kernel Configuration]]) doesn’t need to pick between =amdgpu= and =i915=
itself: it can simply default to =i915= and let this module handle its
own early loading.
#+name: enable-hardware
#+begin_src nix
hardware.amdgpu = {
initrd.enable = true;
opencl.enable = true;
};
#+end_src
Some software expect some packages to be installed by default, such as
rocblas or Hipblas. Here are these packages.
#+name: amd-packages
| Package Name | Description |
|----------------------+--------------------------------------------------------------------|
| =mesa= | Mesa drivers for AMD GPUs |
| =rocmPackages.clr= | Common Language Runtime for ROCm |
| =rocmPackages.clr.icd= | ROCm ICD for OpenCL |
| =rocmPackages.rocblas= | ROCm BLAS library |
| =rocmPackages.hipblas= | HIP BLAS marshalling library (CUDA-compatible interface to rocBLAS) |
| =rocmPackages.rpp= | High-performance computer vision library |
| =nvtopPackages.amd= | Just for me, GPU utilisation monitoring |
#+name: extra-hardware-packages
#+begin_src emacs-lisp :var packages=amd-packages :cache yes
(mapconcat (lambda (package) (s-chop-prefix "=" (s-chop-suffix "=" package)))
(mapcar #'car packages)
"\n")
#+end_src
#+RESULTS[28ba71596b4f184338e46c76c0ba1aa41899bba0]: extra-hardware-packages
: mesa
: rocmPackages.clr
: rocmPackages.clr.icd
: rocmPackages.rocblas
: rocmPackages.hipblas
: rocmPackages.rpp
: nvtopPackages.amd
#+name: hardware-extra-packages
#+begin_src nix
hardware.graphics.extraPackages = with pkgs; [
<<extra-hardware-packages()>>
];
#+end_src
** Diagnostics and Monitoring
Beyond what’s needed by the driver stack itself, I also want a couple
of tools available on the command line to check on the GPU: =clinfo= to
inspect the available OpenCL platforms and devices, =amdgpu_top= for a
=btop=-like view of the AMD GPU’s activity, and =nvtop= (packaged for AMD
under =nvtopPackages.amd=) for a more familiar process-oriented monitor.
#+name: system-packages
#+begin_src nix
environment.systemPackages = with pkgs; [
clinfo
amdgpu_top
nvtopPackages.amd
];
#+end_src
** LACT, the GPU Control Daemon
[[https://github.com/ilya-zlobintsev/LACT][LACT]] (Linux AMDGPU Control Application) lets me tweak fan curves,
power limits and clocks on the card, through a daemon (=lactd=) and a
GUI that talks to it. The package ships both a systemd service
definition and a udev rule, so all that’s left for me to do is install
the package and make sure the daemon is started.
I’m also consolidating the ROCm libraries under =/opt/rocm= via a
=tmpfiles= rule. Some tools out there still expect to find ROCm
installed at that standard filesystem location rather than resolved
through the Nix store, so I build a combined derivation of the ROCm
packages I need and symlink it into place.
#+name: lact
#+begin_src nix
systemd = {
packages = with pkgs; [lact];
services.lactd.wantedBy = ["multi-user.target"];
tmpfiles.rules = let
rocmEnv = pkgs.symlinkJoin {
name = "rocm-combined";
paths = with pkgs.rocmPackages; [
clr
clr.icd
rocblas
hipblas
rpp
];
};
in [
"L+ /opt/rocm - - - - ${rocmEnv}"
];
};
#+end_src
** Environment Variables
Finally, a handful of environment variables to point tools at that
=/opt/rocm= prefix and to steer ROCm/HIP towards the right GPU. This
machine exposes the AMD card as device ID =1= (the other device being an
iGPU), so I pin both =HIP_VISIBLE_DEVICES= and =ROCM_VISIBLE_DEVICES= to
it. The card also isn’t officially supported by ROCm, hence the
=HSA_OVERRIDE_GFX_VERSION= override, which tells ROCm to treat it as the
closest supported architecture instead of refusing to run.
#+name: environment-variables
#+begin_src nix
environment.variables = {
ROCM_PATH = "/opt/rocm";
HIP_VISIBLE_DEVICES = "1";
ROCM_VISIBLE_DEVICES = "1";
HSA_OVERRIDE_GFX_VERSION = "10.3.0";
};
#+end_src
+3 -13
View File
@@ -1,16 +1,6 @@
{
flake.modules.nixos.bluetooth = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.hardware.bluetooth;
in {
options.mySystem.hardware.bluetooth.enable = mkEnableOption "Enable bluetooth";
config = mkIf cfg.enable {
hardware.bluetooth.enable = cfg.enable;
services.blueman.enable = cfg.enable;
};
};
hardware.bluetooth.enable = true;
services.blueman.enable = true;
};
}
+24
View File
@@ -0,0 +1,24 @@
#+title: Bluetooth
#+setupfile: ../headers
* Bluetooth
Not all of my machines have Bluetooth hardware worth turning on, so
this is a plain toggle rather than something applied unconditionally.
Here’s the skeleton of the =nixos.bluetooth= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.bluetooth = {
<<config>>
};
}
#+end_src
** Enabling Bluetooth
Turning the option on enables Bluetooth support at the kernel level
and installs =blueman=, a tray applet I use to pair and manage devices
without digging through a terminal.
#+name: config
#+begin_src nix
hardware.bluetooth.enable = true;
services.blueman.enable = true;
#+end_src
+2 -12
View File
@@ -1,15 +1,5 @@
{
flake.modules.nixos.fingerprint = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.hardware.fingerprint;
in {
options.mySystem.hardware.fingerprint.enable = mkEnableOption "Enable fingerprint reader";
config = mkIf cfg.enable {
hardware.facter.detected.fingerprint.enable = cfg.enable;
};
};
hardware.facter.detected.fingerprint.enable = true;
};
}
+22
View File
@@ -0,0 +1,22 @@
#+title: Fingerprint Reader
#+setupfile: ../headers
* Fingerprint Reader
My ThinkPad x220 has a fingerprint reader, so this is a plain toggle
rather than something applied unconditionally. Here’s the skeleton of
the =nixos.fingerprint= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.fingerprint = {
<<config>>
};
}
#+end_src
** Enabling the Reader
Rather than picking a driver myself, I let [[https://github.com/numtide/nixos-facter-modules][nixos-facter]] detect the
reader and wire up the matching driver automatically.
#+name: config
#+begin_src nix
hardware.facter.detected.fingerprint.enable = true;
#+end_src
+21
View File
@@ -0,0 +1,21 @@
#+title: Firmware
#+setupfile: ../headers
* Firmware
A small module to pull in the full firmware blob set, for machines
where I’d rather not chase down which specific firmware package a
piece of hardware needs. Here’s the =nixos.firmware= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.firmware = {lib, ...}: {
<<enable-all-firmware>>
};
}
#+end_src
=mkDefault= keeps this overridable, in case a host needs to turn it back
off or pin a narrower set of firmware packages itself.
#+name: enable-all-firmware
#+begin_src nix
hardware.enableAllFirmware = lib.mkDefault true;
#+end_src
+5 -13
View File
@@ -1,17 +1,9 @@
{
flake.modules.nixos.corne = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.hardware.input.corne;
in {
options.mySystem.hardware.input.corne.allowHidAccess = mkEnableOption "Enable HID access to the corne keyboard";
config.services.udev = mkIf cfg.allowHidAccess {
extraRules = ''
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{serial}=="*vial:f64c2b3c*", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl"
'';
};
services.udev = {
extraRules = ''
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{serial}=="*vial:f64c2b3c*", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl"
'';
};
};
}
+32
View File
@@ -0,0 +1,32 @@
#+title: Corne Keyboard
#+setupfile: ../../headers
* Corne Keyboard
I use a Corne (=crkbd=), a small split ergonomic keyboard, flashed with
[[https://get.vial.today/][Vial]], a QMK-based firmware that lets me remap keys live from a GUI
instead of having to reflash the keyboard every time I want to tweak
my layout. Here’s the skeleton of the =nixos.corne= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.corne = {
<<udev-rule>>
};
}
#+end_src
** Granting HID Access
By default, the =hidraw= device nodes created for the keyboard are only
accessible to root, which means Vial can’t talk to it without running
as root too. Instead, I add a =udev= rule matching my keyboard’s Vial
identifier (the part after =vial:= is the keyboard’s unique unlock ID,
burned into its firmware) and grant the =users= group read/write access
to it, tagging it for =uaccess= / =udev-acl= so it’s also picked up by the
logged-in session’s ACLs.
#+name: udev-rule
#+begin_src nix
services.udev = {
extraRules = ''
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{serial}=="*vial:f64c2b3c*", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl"
'';
};
#+end_src
@@ -0,0 +1,7 @@
{
flake.modules.nixos.disable-ibm-trackpoint = {
services.udev.extraRules = ''
ATTRS{name}=="*TPPS/2 IBM TrackPoint", ENV{ID_INPUT}="", ENV{ID_INPUT_MOUSE}="", ENV{ID_INPUT_POINTINGSTICK}=""
'';
};
}
@@ -0,0 +1,30 @@
#+title: Disable the IBM TrackPoint
#+setupfile: ../../headers
* Disable the IBM TrackPoint
My ThinkPads come with IBM’s TrackPoint, the little red nub sitting
between the =G=, =H= and =B= keys. I don’t want it active, though, it has a
drift and I cannot fix it unless I change my keyboard. Thus, this
module exposes a way to turn it off. Here’s the skeleton of the
=nixos.disable-ibm-trackpoint= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.disable-ibm-trackpoint = {
<<udev-rule>>
};
}
#+end_src
** Disabling the TrackPoint
The TrackPoint shows up to the input stack as a regular pointer
device, so to disable it I can’t simply blacklist a driver — libinput
and friends would still pick it up through =evdev=. Instead, I match it
by its device name and clear the =ID_INPUT=, =ID_INPUT_MOUSE= and
=ID_INPUT_POINTINGSTICK= udev properties on it, which tells the input
stack to simply ignore the device as a pointing device.
#+name: udev-rule
#+begin_src nix
services.udev.extraRules = ''
ATTRS{name}=="*TPPS/2 IBM TrackPoint", ENV{ID_INPUT}="", ENV{ID_INPUT_MOUSE}="", ENV{ID_INPUT_POINTINGSTICK}=""
'';
#+end_src
-17
View File
@@ -1,17 +0,0 @@
{
flake.modules.nixos.ibm-trackpoint = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.hardware.input.ibmTrackpoint;
in {
options.mySystem.hardware.input.ibmTrackpoint.disable = mkEnableOption "Disable IBM’s trackpoint on ThinkPad";
config.services.udev = mkIf cfg.disable {
extraRules = ''
ATTRS{name}=="*TPPS/2 IBM TrackPoint", ENV{ID_INPUT}="", ENV{ID_INPUT_MOUSE}="", ENV{ID_INPUT_POINTINGSTICK}=""
'';
};
};
}
+5 -15
View File
@@ -1,19 +1,9 @@
{
flake.modules.nixos.opentablet = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.hardware.input.opentablet;
in {
options.mySystem.hardware.input.opentablet.enable = mkEnableOption "Enables OpenTablet drivers";
config = mkIf cfg.enable {
hardware.opentabletdriver = {
inherit (cfg) enable;
daemon.enable = true;
};
boot.kernelModules = ["wacom"];
};
hardware.opentabletdriver = {
enable = true;
daemon.enable = true;
};
boot.kernelModules = ["wacom"];
};
}
+30
View File
@@ -0,0 +1,30 @@
#+title: OpenTabletDriver
#+setupfile: ../../headers
* OpenTabletDriver
Some of my machines are hooked up to a graphics tablet, a Wacom
Bamboo, driven by [[https://opentabletdriver.net/][OpenTabletDriver]]. I remember buying it for 15€, what
a deal that was! Anyway, since most of my hosts don’t have a tablet
attached, this is exposed as a regular toggle rather than enabled
unconditionally. Here’s the skeleton of the =nixos.opentablet= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.opentablet = {
<<config>>
};
}
#+end_src
** Enabling the Driver
Enabling OpenTabletDriver proper is just a matter of turning on the
module and its daemon. I also need to explicitly load the =wacom=
kernel module, since my tablet (like most of them) identifies itself
as a Wacom device at the hardware level regardless of brand.
#+name: config
#+begin_src nix
hardware.opentabletdriver = {
enable = true;
daemon.enable = true;
};
boot.kernelModules = ["wacom"];
#+end_src
+22
View File
@@ -0,0 +1,22 @@
#+title: Trackball
#+setupfile: ../../headers
* Trackball
I use a trackball on some of my machines, and I middle-click by
pressing the left and right buttons together rather than through a
dedicated button. That’s the only downside of the two trackballs I
own; otherwise, I can’t recommend one enough. Here’s the
=nixos.trackball= module enabling this behaviour.
#+begin_src nix :tangle yes
{
flake.modules.nixos.trackball = {
<<middle-emulation>>
};
}
#+end_src
Enabling middle-click emulation is simple:
#+name: middle-emulation
#+begin_src nix
services.libinput.mouse.middleEmulation = true;
#+end_src
+7 -11
View File
@@ -1,14 +1,10 @@
{
flake.modules.nixos.pinetab2 = {lib, ...}:
with lib; {
options.mySystem.hardware.pinetab2.enable = mkEnableOption "Activate support for the PineTab2";
config = {
boot.kernelParams = ["console=tty0" "console=ttyS2,1500000n8" "rootwait" "root=LABEL=NIXOS_SD" "rw"];
hardware.sensor.iio.enable = true;
services.avahi = {
enable = true;
openFirewall = true;
};
};
flake.modules.nixos.pinetab2 = {
boot.kernelParams = ["console=tty0" "console=ttyS2,1500000n8" "rootwait" "root=LABEL=NIXOS_SD" "rw"];
hardware.sensor.iio.enable = true;
services.avahi = {
enable = true;
openFirewall = true;
};
};
}
+48
View File
@@ -0,0 +1,48 @@
#+title: PineTab2
#+setupfile: ../headers
* PineTab2
A few tweaks are specific to my PineTab2 tablet: getting a serial
console working at boot, exposing its sensors, and making it easy to
find on whatever network it’s connected to. Here’s the skeleton of the
=nixos.pinetab2= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.pinetab2 = {
<<kernel-params>>
<<sensors>>
<<avahi>>
};
}
#+end_src
** Serial Console
These kernel parameters get me a working serial console on the
tablet’s UART at boot, and point the kernel at the SD card’s root
filesystem by label rather than by a device path that can shift
around.
#+name: kernel-params
#+begin_src nix
boot.kernelParams = ["console=tty0" "console=ttyS2,1500000n8" "rootwait" "root=LABEL=NIXOS_SD" "rw"];
#+end_src
** Sensors
Turning on the IIO (Industrial I/O) subsystem exposes the tablet’s
accelerometer and ambient light sensor, which is what lets things like
auto-rotate work.
#+name: sensors
#+begin_src nix
hardware.sensor.iio.enable = true;
#+end_src
** Finding It on the Network
The tablet moves between networks a lot, so Avahi lets me reach it by
its =.local= hostname over mDNS instead of having to look up whatever IP
it was handed.
#+name: avahi
#+begin_src nix
services.avahi = {
enable = true;
openFirewall = true;
};
#+end_src
+2 -5
View File
@@ -9,7 +9,6 @@
cfg = config.mySystem.hardware.sound;
in {
options.mySystem.hardware.sound = {
enable = mkEnableOption "Whether to enable sounds with Pipewire";
noisetorch = mkEnableOption "Whether to activate noisetorch support";
scarlett.enable = mkEnableOption "Activate support for Scarlett sound card";
alsa = mkOption {
@@ -35,7 +34,7 @@
config = {
environment.systemPackages = mkIf cfg.scarlett.enable [pkgs.alsa-scarlett-gui];
services = {
pipewire = mkIf cfg.enable {
pipewire = {
enable = true;
alsa = mkIf cfg.alsa {
enable = mkDefault true;
@@ -45,9 +44,7 @@
};
pulseaudio.enable = false;
};
programs.noisetorch = mkIf cfg.enable {
enable = cfg.noisetorch;
};
programs.noisetorch.enable = cfg.noisetorch;
};
};
}
+118
View File
@@ -0,0 +1,118 @@
#+title: Sound
#+setupfile: ../headers
* Sound
I use Pipewire for audio on my desktop machines, with a couple of
compatibility layers and bits of hardware-specific support switched on
as needed. Here’s the skeleton of the =nixos.sound= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.sound = {
lib,
config,
pkgs,
...
}:
with lib; let
cfg = config.mySystem.hardware.sound;
in {
options.mySystem.hardware.sound = {
<<opt-noisetorch>>
<<opt-scarlett>>
<<opt-alsa>>
<<opt-jack>>
<<opt-package>>
};
config = {
<<scarlett-package>>
<<pipewire-config>>
<<noisetorch-config>>
};
};
}
#+end_src
** Noise Suppression
=noisetorch= toggles [[https://github.com/noisetorch/NoiseTorch][NoiseTorch]], a real-time microphone noise
suppression tool I use for calls, so I have an option for that.
#+name: opt-noisetorch
#+begin_src nix
noisetorch = mkEnableOption "Whether to activate noisetorch support";
#+end_src
Passing it to NixOS is quite simple.
#+name: noisetorch-config
#+begin_src nix
programs.noisetorch.enable = cfg.noisetorch;
#+end_src
** Scarlett Sound Card
=scarlett.enable= is for the machine =marpa= with a Focusrite Scarlett 2i2
audio interface plugged in; it only pulls in that card’s control GUI.
#+name: opt-scarlett
#+begin_src nix
scarlett.enable = mkEnableOption "Activate support for Scarlett sound card";
#+end_src
It is installed quite easily in enabled.
#+name: scarlett-package
#+begin_src nix
environment.systemPackages = mkIf cfg.scarlett.enable [pkgs.alsa-scarlett-gui];
#+end_src
** Enabling Pipewire
Enabling Pipewire also means explicitly turning PulseAudio off, since
the two can’t run as the system’s sound server at the same time.
#+name: pipewire-config
#+begin_src nix
services = {
pipewire = {
enable = true;
alsa = mkIf cfg.alsa {
enable = mkDefault true;
support32Bit = mkDefault true;
};
jack.enable = mkDefault cfg.jack;
};
pulseaudio.enable = false;
};
#+end_src
** ALSA Compatibility
=alsa= enables Pipewire’s ALSA compatibility layer, on by default since
most of my audio software still expects ALSA.
#+name: opt-alsa
#+begin_src nix
alsa = mkOption {
type = types.bool;
example = true;
default = true;
description = "Whether to enable ALSA support with Pipewire";
};
#+end_src
** JACK Compatibility
=jack= enables Pipewire’s JACK compatibility layer, off by default since
only my production machine needs it.
#+name: opt-jack
#+begin_src nix
jack = mkOption {
type = types.bool;
example = true;
default = false;
description = "Whether to enable JACK support with Pipewire";
};
#+end_src
** Base Package
=package= picks which PulseAudio package to build things on top of.
#+name: opt-package
#+begin_src nix
package = mkOption {
type = types.package;
example = pkgs.pulseaudio;
default = pkgs.pulseaudioFull;
description = "Which base package to use for PulseAudio";
};
#+end_src
+22
View File
@@ -0,0 +1,22 @@
# -*- mode: org -*-
#+AUTHOR: Lucien Cartier-Tilet
#+EMAIL: lucien@phundrak.com
#+CREATOR: Lucien Cartier-Tilet
#+LANGUAGE: en
# ### ORG OPTIONS ##############################################################
#+options: H:4 broken_links:mark email:t ^:{} tex:dvisvgm toc:nil
#+KEYWORDS: dotfiles, linux, emacs, configuration, phundrak, drakpa
#+startup: content align hideblocks
#+property: header-args:emacs-lisp :noweb yes :exports none :eval yes :cache yes
#+property: header-args:nix :exports code :tangle no :noweb no-export
#+property: header-args:dot :dir img :exports results :eval yes :cache yes :class gentree
# ### MACROS ###################################################################
#+macro: phon @@html:/$1/@@
#+macro: newline @@html:<br>@@
#+macro: latex-html @@latex:$1@@@@html:$2@@
#+macro: v @@html:<span class=vertical>$1</span>@@
#+macro: begin-largetable @@html:<div class="largetable">@@
#+macro: end-largetable @@html:</div>@@
+1 -1
View File
@@ -1,6 +1,6 @@
{inputs, ...}: {
flake-file.inputs.caelestia-shell = {
url = "github:caelestia-dots/shell";
url = "github:caelestia-dots/shell?ref=stable";
inputs.nixpkgs.follows = "nixpkgs";
};
@@ -62,6 +62,7 @@
openmw
openttd-jgrpp
moonlight-qt
vintagestory
# Gnome stuff
gnomeExtensions.tray-icons-reloaded
+1 -4
View File
@@ -27,10 +27,7 @@ in {
mySystem = {
boot = {
kernel = {
hardened = true;
cpuVendor = "amd";
};
kernel.cpuVendor = "amd";
grub = {
enable = true;
device = "/dev/sdb";
+3 -8
View File
@@ -9,6 +9,7 @@ in {
m.kernel
m.hardened
m.loader
m.zfs
m.docker
m.endlessh
m.ssh
@@ -22,18 +23,12 @@ in {
mySystem = {
boot = {
kernel = {
hardened = true;
cpuVendor = "intel";
};
kernel.cpuVendor = "intel";
grub = {
enable = true;
device = "/dev/sdh";
};
zfs = {
enable = true;
pools = ["tank"];
};
zfs.pools = ["tank"];
};
dev.docker = {
enable = true;
+4 -20
View File
@@ -19,7 +19,7 @@ in {
m.bluetooth
m.fingerprint
m.corne
m.ibm-trackpoint
m.disable-ibm-trackpoint
m.opentablet
m.sound
m.i18n-input
@@ -32,7 +32,6 @@ in {
mySystem = {
boot = {
plymouth.enable = true;
kernel = {
cpuVendor = "intel";
package = pkgs.linuxPackages;
@@ -51,29 +50,14 @@ in {
podman.enable = true;
autoprune.enable = true;
};
hardware = {
bluetooth.enable = true;
fingerprint.enable = true;
input = {
corne.allowHidAccess = true;
ibmTrackpoint.disable = true;
opentablet.enable = true;
};
sound.enable = true;
};
i18n.input.enable = true;
misc.keymap = "fr-bepo";
networking = {
hostname = "gampo";
id = "0630b33f";
};
packages = {
appimage.enable = true;
flatpak.enable = true;
nix = {
gc.automatic = true;
nix-ld.enable = true;
};
packages.nix = {
gc.automatic = true;
nix-ld.enable = true;
};
services = {
fwupd.enable = true;
+3 -16
View File
@@ -17,6 +17,7 @@ in {
# m.niri
m.waydroid
m.xserver
m.amdgpu
m.docker
m.qemu
m.bluetooth
@@ -72,7 +73,6 @@ in {
mySystem = {
boot = {
plymouth.enable = true;
kernel = {
cpuVendor = "amd";
v4l2loopback.enable = true;
@@ -84,11 +84,11 @@ in {
};
desktop = {
hyprland.enable = true;
niri.enable = true;
waydroid.enable = true;
xserver = {
enable = true;
de = "gnome";
videoDrivers = ["amdgpu"];
};
};
dev = {
@@ -97,23 +97,14 @@ in {
podman.enable = true;
autoprune.enable = true;
};
qemu.enable = true;
};
hardware = {
amdgpu.enable = true;
bluetooth.enable = true;
input = {
corne.allowHidAccess = true;
opentablet.enable = true;
};
sound = {
enable = true;
noisetorch = true;
jack = true;
scarlett.enable = true;
};
};
i18n.input.enable = true;
misc.keymap = "fr-bepo";
networking = {
hostname = "marpa";
@@ -126,11 +117,7 @@ in {
}
];
};
packages = {
appimage.enable = true;
flatpak.enable = true;
nix.nix-ld.enable = true;
};
packages.nix.nix-ld.enable = true;
services = {
fwupd.enable = true;
harmonia = {
+3 -15
View File
@@ -9,7 +9,6 @@ in {
m.niri
m.waydroid
m.xserver
m.amdgpu
m.docker
m.bluetooth
m.opentablet
@@ -38,26 +37,15 @@ in {
podman.enable = true;
autoprune.enable = true;
};
hardware = {
bluetooth.enable = true;
input.opentablet.enable = true;
pinetab2.enable = true;
sound.enable = true;
};
i18n.input.enable = true;
misc.keymap = "fr-bepo";
networking = {
hostname = "pinetab2";
id = "99a11b15";
wifi.disablePowersave = true;
};
packages = {
appimage.enable = true;
flatpak.enable = true;
nix = {
gc.automatic = true;
nix-ld.enable = true;
};
packages.nix = {
gc.automatic = true;
nix-ld.enable = true;
};
services.ssh.enable = true;
users = {
+3 -8
View File
@@ -8,6 +8,7 @@ in {
m.kernel
m.hardened
m.loader
m.zfs
m.docker
m.calibre
m.endlessh
@@ -18,14 +19,8 @@ in {
mySystem = {
boot = {
kernel = {
hardened = true;
cpuVendor = "amd";
};
zfs = {
enable = true;
pools = ["tank"];
};
kernel.cpuVendor = "amd";
zfs.pools = ["tank"];
};
dev.docker.enable = true;
misc.keymap = "fr-bepo";
+14 -24
View File
@@ -1,27 +1,17 @@
{
flake.modules.nixos.i18n-input = {
lib,
config,
pkgs,
...
}:
with lib; let
cfg = config.mySystem.i18n.input;
in {
options.mySystem.i18n.input.enable = mkEnableOption "Enable i18n input with fcitx5";
config.i18n.inputMethod = mkIf cfg.enable {
enable = true;
type = "fcitx5";
fcitx5.addons = with pkgs; [
fcitx5-gtk
fcitx5-mozc-ut # Japanese input support
fcitx5-nord
fcitx5-table-other # X-SAMPA to IPA support
qt6Packages.fcitx5-chinese-addons # allow to load table addons
qt6Packages.fcitx5-configtool
qt6Packages.fcitx5-with-addons
];
};
flake.modules.nixos.i18n-input = {pkgs, ...}: {
i18n.inputMethod = {
enable = true;
type = "fcitx5";
fcitx5.addons = with pkgs; [
fcitx5-gtk
fcitx5-mozc-ut
fcitx5-nord
fcitx5-table-other
qt6Packages.fcitx5-chinese-addons
qt6Packages.fcitx5-configtool
qt6Packages.fcitx5-with-addons
];
};
};
}
+52
View File
@@ -0,0 +1,52 @@
#+title: Input Methods
#+setupfile: ../headers
* Input Methods
I type in more than one script, so I need a proper input method
framework rather than relying on whatever the desktop environment
ships with. Plus, the default /AFNOR bépo/ layout is currently buggy,
with some dead keys not working properly, such as =AltGr+s=, but it
isn’t with fcitx5. Here’s the =nixos.i18n-input= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.i18n-input = {pkgs, ...}: {
i18n.inputMethod = {
<<enable-fcitx5>>
<<fcitx5-addons>>
};
};
}
#+end_src
** Enabling fcitx5
[[https://fcitx-im.org/wiki/Fcitx_5][fcitx5]] is the input method framework I’ve settled on; it covers
everything from CJK input to custom table-based methods.
#+name: enable-fcitx5
#+begin_src nix
enable = true;
type = "fcitx5";
#+end_src
** Addons
=fcitx5-gtk= gets fcitx5 working inside GTK applications, and the Qt
equivalents (=fcitx5-configtool=, =fcitx5-with-addons=) do the same for Qt
ones whilst also giving me a GUI to configure it all. =fcitx5-mozc-ut=
adds Japanese input through Mozc, and
=qt6Packages.fcitx5-chinese-addons= adds the table addons Chinese input
methods need. I don’t actually need this package for Chinese itself,
but it is somehow necessary for the X-SAMPA input method, which I get
from =fcitx5-table-other=. I have it to type IPA on the fly when working
on my constructed languages. Lastly, =fcitx5-nord= just reskins the UI
to match the rest of my setup.
#+name: fcitx5-addons
#+begin_src nix
fcitx5.addons = with pkgs; [
fcitx5-gtk
fcitx5-mozc-ut
fcitx5-nord
fcitx5-table-other
qt6Packages.fcitx5-chinese-addons
qt6Packages.fcitx5-configtool
qt6Packages.fcitx5-with-addons
];
#+end_src
+45
View File
@@ -0,0 +1,45 @@
#+title: Locale
#+setupfile: ../headers
* Locale
I want my system messages in English, but everything else about how
dates, money and paper sizes are formatted to reflect where I actually
live. Here’s the =nixos.locale= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.locale = {
i18n = {
<<default-locale>>
<<extra-locale-settings>>
};
};
}
#+end_src
** Default Locale
=en_DK.UTF-8= is a well-known trick: English messages and collation, but
ISO 8601 dates and sane metric units, instead of =en_US='s nonsense
MM/DD/YYYY and imperial units.
#+name: default-locale
#+begin_src nix
defaultLocale = "en_DK.UTF-8";
#+end_src
** Regional Settings
Since I live in France, I want addresses, money, paper size, phone
numbers and the like to follow French conventions instead of whatever
=en_DK= would otherwise pick.
#+name: extra-locale-settings
#+begin_src nix
extraLocaleSettings = {
LC_ADDRESS = "fr_FR.UTF-8";
LC_IDENTIFICATION = "fr_FR.UTF-8";
LC_MEASUREMENT = "fr_FR.UTF-8";
LC_MONETARY = "fr_FR.UTF-8";
LC_NAME = "fr_FR.UTF-8";
LC_NUMERIC = "fr_FR.UTF-8";
LC_PAPER = "fr_FR.UTF-8";
LC_TELEPHONE = "fr_FR.UTF-8";
LC_TIME = "fr_FR.UTF-8";
};
#+end_src
+342
View File
@@ -0,0 +1,342 @@
#+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 having [[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 possibility 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
+1 -1
View File
@@ -11,5 +11,5 @@
};
};
flake-file.outputs = "dendritic";
flake-file.description = "NixOS and Home Manager configuration of phundrak";
flake-file.description = "NixOS and Home Manager configuration of P'undrak";
}
+82
View File
@@ -0,0 +1,82 @@
#+title: Flake File Setup
#+setupfile: headers
* Flake File Setup
This configuration uses [[https://flake-file.denful.dev/][flake-file]]. This means my =flake.nix= file is
modular, as it allows me to define my inputs where I need them, and
not necessarily all at the same place. This can be a bit unusual for
people who are new to it, but trust me, it’s well worth it.
I’ll simply set up the outputs, a single one named =dendritic=, and the
description here, as nothing is too complicated.
#+begin_src nix :tangle yes
{
<<inputs>>
flake-file.outputs = "dendritic";
flake-file.description = "NixOS and Home Manager configuration of P'undrak";
}
#+end_src
** Inputs
Some flake imputs are required globally, as they are used by default
by this configuration and some hosts.
#+name: inputs
#+begin_src nix
flake-file.inputs = {
<<inputs-nixpkgs>>
<<inputs-flake-parts>>
<<inputs-flake-file>>
<<inputs-import-tree>>
<<inputs-home-manager>>
};
#+end_src
First, we get to nixpkgs, which
is quite necessary to get NixOS up and running: it gives access to
Nix’s packages. I also have a =nixpkgsStable= input for the kernel of my
PineTab 2 tablet. Otherwise, I use Nix unstable.
#+name: inputs-nixpkgs
#+begin_src nix
nixpkgsStable.url = "nixpkgs/nixos-25.11";
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
#+end_src
Next, I need some inputs for my dendritic config. =flake-parts= is the
heart of it: it’s a framework for writing flake modules that can
easily be put together as a single module defining an entire system,
such as a home configuration or a host configuration.
#+name: inputs-flake-parts
#+begin_src nix
flake-parts.url = "github:hercules-ci/flake-parts";
#+end_src
Next, we have flake-file, which permits the modularisation of my
=flake.nix= file itself, as mentioned abve.
#+name: inputs-flake-file
#+begin_src nix
flake-file.url = "github:vic/flake-file";
#+end_src
Next, we have =import-tree=, which automatically imports my Nix files,
making all modules globally known.
#+name: inputs-import-tree
#+begin_src nix
import-tree.url = "github:vic/import-tree";
#+end_src
And last but not least, =home-manager=. This allows me to manage the
programs of me as the user of my machine and not necessarily the
machine itself, as well as their configuration. As such, I can upgrade
a machine’s system without touching my environment, and the inverse is
true. However, it’s important to keep them in sync when it comes to
major upgrades of NixOS, so home-manager will follow the system’s
version.
#+name: inputs-home-manager
#+begin_src nix
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
#+end_src
+12 -16
View File
@@ -7,31 +7,27 @@
with lib; let
cfg = config.mySystem.misc;
in {
options.mySystem.misc = {
timezone = mkOption {
type = types.str;
default = "Europe/Paris";
};
keymap = mkOption {
type = types.str;
default = "fr";
example = "fr-bepo";
description = "Keymap to use in the TTY console";
};
options.mySystem.misc.timezone = mkOption {
type = types.str;
default = "Europe/Paris";
};
options.mySystem.misc.keymap = mkOption {
type = types.str;
default = "fr";
example = "fr-bepo";
description = "Keymap to use in the TTY console";
};
config = {
boot.tmp.cleanOnBoot = true;
console.keyMap = cfg.keymap;
time.timeZone = cfg.timezone;
services.envfs.enable = true;
services.orca.enable = false;
boot.tmp.cleanOnBoot = true;
environment.pathsToLink = [
"/share/bash-completion"
"/share/zsh"
];
services = {
orca.enable = false;
envfs.enable = true;
};
};
};
}
+109
View File
@@ -0,0 +1,109 @@
#+title: Misc NixOS Configurations
#+setupfile: headers
* Misc NixOS Configurations
This module hosts some misc configuration I am unsure where to put
elsewhere than here. Don’t expect this file to be coherent, but expect
it to be quite short.
This module declares the aspect =nixos.misc=, which is used by all my
hosts.
#+begin_src nix :tangle yes
{
flake.modules.nixos.misc = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.misc;
in {
<<timezone-option>>
<<layout-option>>
config = {
<<layout-config>>
<<timezone-config>>
<<envfs>>
<<orca>>
<<clean-tmp>>
<<completion>>
};
};
}
#+end_src
** System Timezone
First, let me declare the timezone. I’ll set it as an option, as not
all my machines live in the same place, but I’ll default to
Metropolitan France’s timezone.
#+name: timezone-option
#+begin_src nix
options.mySystem.misc.timezone = mkOption {
type = types.str;
default = "Europe/Paris";
};
#+end_src
Now, I can set it for my system.
#+name: timezone-config
#+begin_src nix
time.timeZone = cfg.timezone;
#+end_src
** TTY Keyboard Layout
Now, I can set the TTY’s keyboard config. Most of my machines use the
Bépo layout everywhere, but I do share one whose owner doesn’t use it,
so I’ll let this as an option to be set, defaulting to the default
French layout.
#+name: layout-option
#+begin_src nix
options.mySystem.misc.keymap = mkOption {
type = types.str;
default = "fr";
example = "fr-bepo";
description = "Keymap to use in the TTY console";
};
#+end_src
Now, I can set it on my system.
#+name: layout-config
#+begin_src nix
console.keyMap = cfg.keymap;
#+end_src
** Truly Miscelaneous Config
First, some scripts are written with the assumption that some
utilities are always in the same place, such as =/bin/bash=. It is,
however, not always the case with NixOS. Mic92’s [[https://github.com/Mic92/envfs][envfs]] solves that.
#+name: envfs
#+begin_src nix
services.envfs.enable = true;
#+end_src
I have no idea why, but sometimes, [[https://orca.gnome.org/][Orca]] get activated without my
consent. So I hard-disable it here.
#+name: orca
#+begin_src nix
services.orca.enable = false;
#+end_src
I was surprised to see =/tmp= still hold the same files after my first
reboot in NixOS, I always assumed it was cleared on every reboot on
every system. But I can enable this behaviour back.
#+name: clean-tmp
#+begin_src nix
boot.tmp.cleanOnBoot = true;
#+end_src
Lastly, I want to make sure my shell completions work, so I’ll enable
this.
#+name: completion
#+begin_src nix
environment.pathsToLink = [
"/share/bash-completion"
"/share/zsh"
];
#+end_src
+61 -65
View File
@@ -7,74 +7,70 @@
with lib; let
cfg = config.mySystem.networking;
in {
options.mySystem.networking = with types; {
hostname = mkOption {
type = str;
example = "gampo";
};
id = mkOption {
type = str;
example = "deadb33f";
};
domain = mkOption {
type = nullOr str;
example = "phundrak.com";
default = null;
};
hostFiles = mkOption {
type = listOf path;
example = [/path/to/hostFile];
default = [];
};
firewall = {
openPorts = mkOption {
type = listOf int;
example = [22 80 443];
default = [];
};
openPortRanges = mkOption {
type = listOf (attrsOf port);
default = [];
example = [
{
from = 8080;
to = 8082;
}
];
description = ''
A range of TCP and UDP ports on which incoming connections are
accepted.
'';
};
extraCommands = mkOption {
type = nullOr lines;
example = "iptables -A INPUTS -p icmp -j ACCEPT";
default = null;
};
};
wifi.disablePowersave = mkEnableOption ''
Disables powersave for Wifi.
options.mySystem.networking.hostname = mkOption {
type = types.str;
example = "gampo";
};
config.networking.hostName = cfg.hostname;
options.mySystem.networking.id = mkOption {
type = types.str;
example = "deadb33f";
};
config.networking.hostId = cfg.id;
options.mySystem.networking.domain = mkOption {
type = types.nullOr types.str;
example = "phundrak.com";
default = null;
};
config.networking.domain = cfg.domain;
options.mySystem.networking.hostFiles = mkOption {
type = types.listOf types.path;
example = [/path/to/hostFile];
default = [];
};
config.networking.hostFiles = cfg.hostFiles;
options.mySystem.networking.wifi.disablePowersave = mkEnableOption ''
Disables powersave for Wifi.
Used mainly for the PineTab2, as leaving WiFi powersave with the bes2600 can cause stability issues.
Used mainly for the PineTab2, as leaving WiFi powersave with the bes2600 can cause stability issues.
'';
config.networking.networkmanager = {
enable = true;
wifi.powersave = !cfg.wifi.disablePowersave;
};
options.mySystem.networking.firewall.openPorts = mkOption {
type = types.listOf types.int;
example = [22 80 443];
default = [];
};
config.networking.firewall = {
enable = true;
allowedTCPPorts = cfg.firewall.openPorts;
allowedUDPPorts = cfg.firewall.openPorts;
};
options.mySystem.networking.firewall.openPortRanges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [];
example = [
{
from = 8080;
to = 8082;
}
];
description = ''
A range of TCP and UDP ports on which incoming connections are
accepted.
'';
};
config.networking = {
hostName = cfg.hostname; # Define your hostname.
hostId = cfg.id;
networkmanager = {
enable = true;
wifi.powersave = ! cfg.wifi.disablePowersave;
};
inherit (cfg) hostFiles domain;
firewall = {
enable = true;
allowedTCPPorts = cfg.firewall.openPorts;
allowedUDPPorts = cfg.firewall.openPorts;
allowedTCPPortRanges = cfg.firewall.openPortRanges;
allowedUDPPortRanges = cfg.firewall.openPortRanges;
extraCommands = (mkIf (cfg.firewall.extraCommands != null)) cfg.firewall.extraCommands;
};
config.networking.firewall = {
allowedTCPPortRanges = cfg.firewall.openPortRanges;
allowedUDPPortRanges = cfg.firewall.openPortRanges;
};
options.mySystem.networking.firewall.extraCommands = mkOption {
type = types.nullOr types.lines;
example = "iptables -A INPUTS -p icmp -j ACCEPT";
default = null;
};
config.networking.firewall.extraCommands = mkIf (cfg.firewall.extraCommands != null) cfg.firewall.extraCommands;
};
}
+151
View File
@@ -0,0 +1,151 @@
#+title: Networking
#+setupfile: ../headers
* Networking
This module centralises the basic networking identity of a host —
hostname, host ID, domain, NetworkManager and the firewall — behind a
single =mySystem.networking= namespace. Here’s the skeleton of the
=nixos.networking= module.
#+begin_src nix :tangle yes
{...}: {
flake.modules.nixos.networking = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.networking;
in {
<<hostname>>
<<host-id>>
<<domain>>
<<host-files>>
<<wifi-powersave>>
<<firewall-ports>>
<<firewall-port-ranges>>
<<firewall-extra-commands>>
};
}
#+end_src
** Hostname
#+name: hostname
#+begin_src nix
options.mySystem.networking.hostname = mkOption {
type = types.str;
example = "gampo";
};
config.networking.hostName = cfg.hostname;
#+end_src
** Host ID
Every host needs a unique =hostId=; besides identifying the machine on
the network, ZFS also uses it to guard against accidentally importing
a pool that’s still active on another machine (see [[file:../boot/zfs.org][ZFS Support]]).
#+name: host-id
#+begin_src nix
options.mySystem.networking.id = mkOption {
type = types.str;
example = "deadb33f";
};
config.networking.hostId = cfg.id;
#+end_src
** Domain
Not every host needs a domain name, so this defaults to =null=.
#+name: domain
#+begin_src nix
options.mySystem.networking.domain = mkOption {
type = types.nullOr types.str;
example = "phundrak.com";
default = null;
};
config.networking.domain = cfg.domain;
#+end_src
** Extra Host Files
=hostFiles= lets a host point at extra files to merge into =/etc/hosts=,
on top of whatever NixOS generates itself.
#+name: host-files
#+begin_src nix
options.mySystem.networking.hostFiles = mkOption {
type = types.listOf types.path;
example = [/path/to/hostFile];
default = [];
};
config.networking.hostFiles = cfg.hostFiles;
#+end_src
** NetworkManager and WiFi Powersave
NetworkManager is always enabled; the only thing a host can tune here
is WiFi powersave. I mainly need to turn it off on the PineTab2, since
leaving powersave on with its =bes2600= WiFi chip causes stability
issues.
#+name: wifi-powersave
#+begin_src nix
options.mySystem.networking.wifi.disablePowersave = mkEnableOption ''
Disables powersave for Wifi.
Used mainly for the PineTab2, as leaving WiFi powersave with the bes2600 can cause stability issues.
'';
config.networking.networkmanager = {
enable = true;
wifi.powersave = !cfg.wifi.disablePowersave;
};
#+end_src
** Firewall: Ports
This also turns the firewall itself on; =openPorts= is just the plain
list of single ports to open, on both TCP and UDP.
#+name: firewall-ports
#+begin_src nix
options.mySystem.networking.firewall.openPorts = mkOption {
type = types.listOf types.int;
example = [22 80 443];
default = [];
};
config.networking.firewall = {
enable = true;
allowedTCPPorts = cfg.firewall.openPorts;
allowedUDPPorts = cfg.firewall.openPorts;
};
#+end_src
** Firewall: Port Ranges
=openPortRanges= does the same, but for a contiguous range of ports at
once, again on both TCP and UDP.
#+name: firewall-port-ranges
#+begin_src nix
options.mySystem.networking.firewall.openPortRanges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [];
example = [
{
from = 8080;
to = 8082;
}
];
description = ''
A range of TCP and UDP ports on which incoming connections are
accepted.
'';
};
config.networking.firewall = {
allowedTCPPortRanges = cfg.firewall.openPortRanges;
allowedUDPPortRanges = cfg.firewall.openPortRanges;
};
#+end_src
** Firewall: Extra Commands
For anything the declarative options above can’t express, =extraCommands=
lets a host drop raw =iptables= commands straight into the firewall
setup.
#+name: firewall-extra-commands
#+begin_src nix
options.mySystem.networking.firewall.extraCommands = mkOption {
type = types.nullOr types.lines;
example = "iptables -A INPUTS -p icmp -j ACCEPT";
default = null;
};
config.networking.firewall.extraCommands = mkIf (cfg.firewall.extraCommands != null) cfg.firewall.extraCommands;
#+end_src
+9 -22
View File
@@ -1,25 +1,12 @@
{...}: {
{
flake.modules.nixos.tailscale = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.network.tailscale;
in {
options.mySystem.network.tailscale = {
enable = mkOption {
type = types.bool;
default = true;
};
};
config.services.tailscale = {
inherit (cfg) enable;
extraSetFlags = [
"--accept-dns"
"--accept-routes"
"--ssh"
];
};
services.tailscale = {
enable = true;
extraSetFlags = [
"--accept-dns"
"--accept-routes"
"--ssh"
];
};
};
}
+52
View File
@@ -0,0 +1,52 @@
#+title: Tailscale
#+setupfile: ../headers
* Tailscale
I use [[https://tailscale.com/][Tailscale]] as the mesh VPN tying all my machines together. Here’s
the =nixos.tailscale= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.tailscale = {
services.tailscale = {
<<enable>>
<<extra-flags>>
};
};
}
#+end_src
** Enabling Tailscale
#+name: enable
#+begin_src nix
enable = true;
#+end_src
** Extra Flags
I add some extra flags for Tailscale.
#+name: flags
| Flag | Why |
|-----------------+---------------------------------------------------------|
| =--accept-dns= | Turns on MagicDNS |
| =--accept-routes= | Let this machine use subnets advertised by other nodes |
| =--ssh= | Turns on Tailscale’s own SSH server for easy SSH access |
#+name: make-flags
#+begin_src emacs-lisp :exports none :var flags=flags :cache yes
(mapconcat (lambda (flag)
(replace-regexp-in-string "=" "\"" (car flag)))
flags
"\n")
#+end_src
#+RESULTS[4969e8a817fa6617e136b57d47a43d6e21ce2a7b]: make-flags
: "--accept-dns"
: "--accept-routes"
: "--ssh"
#+name: extra-flags
#+begin_src nix
extraSetFlags = [
<<make-flags()>>
];
#+end_src
+12 -18
View File
@@ -1,5 +1,5 @@
{...}: let
cacheSettings = {
let
cacheSettings = rec {
substituters = [
"https://phundrak.cachix.org?priority=10"
"https://nix-community.cachix.org?priority=20"
@@ -10,28 +10,22 @@
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
];
commonConf = {
extra-trusted-public-keys = trustedPublicKeys;
extra-substituters = substituters;
extra-experimental-features = ["nix-command" "flakes"];
http-connections = 128;
};
};
in {
flake-file.nixConfig = {
extra-trusted-public-keys = cacheSettings.trustedPublicKeys;
extra-substituters = cacheSettings.substituters;
extra-experimental-features = ["nix-command" "flakes"];
http-connections = 128;
};
flake.nixConfig = {
extra-trusted-public-keys = cacheSettings.trustedPublicKeys;
extra-substituters = cacheSettings.substituters;
extra-experimental-features = ["nix-command" "flakes"];
http-connections = 128;
};
flake-file.nixConfig = cacheSettings.commonConf;
flake.nixConfig = cacheSettings.commonConf;
flake.modules.nixos.nix-cache-settings = {
nix.settings = {
substituters = cacheSettings.substituters;
inherit (cacheSettings) substituters;
trusted-public-keys = cacheSettings.trustedPublicKeys;
http-connections = 128;
experimental-features = ["nix-command" "flakes"];
http-connections = 128;
};
};
}
+81
View File
@@ -0,0 +1,81 @@
#+title: Nix Configuration
#+setupfile: headers
#+property: header-args:emacs-lisp :lexical t :exports none :tangle no
* Nix Configuration
Something that I want to enable everywhere is, first and foremost, the
support for Nix commands and Nix flakes. I also want to make sure I
can use some caches, also known as substituters, including one of mine
from Cachix, to avoid compiling stuff as much as possible.
So first, here are my caches with their public key.
#+name: substituters
| Substituter's URL | Public Key |
|----------------------------------------------+-------------------------------------------------------------------------|
| https://phundrak.cachix.org?priority=10 | =phundrak.cachix.org-1:osJAkYO0ioTOPqaQCIXMfIRz1/+YYlVFkup3R2KSexk== |
| https://nix-community.cachix.org?priority=20 | =nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs== |
| https://cache.nixos.org?priority=40 | =cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY== |
#+name: substituters-urls
#+begin_src emacs-lisp :var subs=substituters :cache yes
(mapconcat (lambda (sub) (format "\"%s\"" (car sub))) subs "\n")
#+end_src
#+RESULTS[99d05698d7e8ca90b34823f4dd4858224be8d007]: substituters-urls
: "https://phundrak.cachix.org?priority=10"
: "https://nix-community.cachix.org?priority=20"
: "https://cache.nixos.org?priority=40"
#+name: substituters-keys
#+begin_src emacs-lisp :var subs=substituters :cache yes
(require 's)
(mapconcat (lambda (sub) (format "\"%s\""
(s-chop-prefix "=" (s-chop-suffix "=" (cadr sub)))))
subs
"\n")
#+end_src
#+RESULTS[6f5e709a7b1c1dd55e4187dfaf8be945138c68ec]: substituters-keys
: "phundrak.cachix.org-1:osJAkYO0ioTOPqaQCIXMfIRz1/+YYlVFkup3R2KSexk="
: "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
: "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
This results in the following substituters and trusted public keys.
#+name: config-vars
#+begin_src nix :noweb yes
substituters = [
<<substituters-urls()>>
];
trustedPublicKeys = [
<<substituters-keys()>>
];
#+end_src
Now, we can declare the rest of the file. You’ll see, it repeats
itself a bit.
#+begin_src nix :tangle yes
let
cacheSettings = rec {
<<config-vars>>
commonConf = {
extra-trusted-public-keys = trustedPublicKeys;
extra-substituters = substituters;
extra-experimental-features = ["nix-command" "flakes"];
http-connections = 128;
};
};
in {
flake-file.nixConfig = cacheSettings.commonConf;
flake.nixConfig = cacheSettings.commonConf;
flake.modules.nixos.nix-cache-settings = {
nix.settings = {
inherit (cacheSettings) substituters;
trusted-public-keys = cacheSettings.trustedPublicKeys;
experimental-features = ["nix-command" "flakes"];
http-connections = 128;
};
};
}
#+end_src
+4 -12
View File
@@ -1,16 +1,8 @@
{
flake.modules.nixos.appimage = {
lib,
config,
...
}:
with lib; let
cfg = config.mySystem.packages.appimage;
in {
options.mySystem.packages.appimage.enable = mkEnableOption "Enables AppImage support";
config.programs.appimage = mkIf cfg.enable {
inherit (cfg) enable;
binfmt = true;
};
programs.appimage = {
enable = true;
binfmt = true;
};
};
}
+24
View File
@@ -0,0 +1,24 @@
#+title: AppImage
#+setupfile: ../headers
* AppImage
A small module to let AppImages run directly, without having to
extract or wrap them by hand first. Here’s the =nixos.appimage= module.
#+begin_src nix :tangle yes
{
flake.modules.nixos.appimage = {
<<enable-appimage>>
};
}
#+end_src
=binfmt= registers AppImages with the kernel’s =binfmt_misc=, so running
one is as simple as executing it directly, the same as any other
binary.
#+name: enable-appimage
#+begin_src nix
programs.appimage = {
enable = true;
binfmt = true;
};
#+end_src
+2 -5
View File
@@ -8,11 +8,8 @@
with lib; let
cfg = config.mySystem.packages.flatpak;
in {
options.mySystem.packages.flatpak = {
enable = mkEnableOption "Enable Flatpak support";
builder.enable = mkEnableOption "Enable Flatpak builder";
};
config = mkIf cfg.enable {
options.mySystem.packages.flatpak.builder.enable = mkEnableOption "Enable Flatpak builder";
config = {
environment.systemPackages = lists.optional cfg.builder.enable pkgs.flatpak-builder;
services.flatpak.enable = true;
};