forked from
lass.pds.lassul.us/superconfig
This repository has no description
7.9 kB
196 lines
1{
2 modulesPath,
3 pkgs,
4 lib,
5 ...
6}:
7{
8 imports = [
9 ./config.nix
10 (modulesPath + "/installer/scan/not-detected.nix")
11 # Two boot media from one config. Every entry in image.modules is applied
12 # to this config through its own extendModules, so the iso and netboot
13 # variants never see each other's fileSystems / bootloader definitions —
14 # no disabledModules or mkForce juggling needed.
15 #
16 # nixos-rebuild build-image --flake .#atlas --image-variant iso
17 # nix build .#nixosConfigurations.atlas.config.system.build.images.iso
18 # nix build .#atlas-netboot-root (kernel+initrd+ipxe for PXE)
19 (modulesPath + "/image/images.nix")
20 ];
21
22 # Deliberately no image module at the toplevel: nixpkgs warns that doing so
23 # defines system.build.image and leaks the medium into every variant. That
24 # leaves the base config without a real boot medium, so these two
25 # placeholders exist only to let it evaluate; both image variants override
26 # the root with mkImageMediaOverride (priority 60), which wins over these.
27 # The base toplevel itself is never booted or deployed.
28 fileSystems."/" = {
29 device = "tmpfs";
30 fsType = "tmpfs";
31 };
32 boot.loader.grub.enable = lib.mkDefault false;
33
34 # Live USB stick: store is a squashfs on the stick, root a tmpfs overlay on
35 # top. Stateless, nothing installed — dd it and boot.
36 #
37 # The built-in `iso` variant is iso-image.nix (not installation-cd-base):
38 # no nixos channel, no installer profile, no `nixos` autologin to fight
39 # greetd or clash with configs/pinned-registry.nix. Settings here merge
40 # into it, since image.modules is an attrsOf deferredModule.
41 image.modules.iso = {
42 # zstd keeps the squashfs small without a slow xz build.
43 isoImage.squashfsCompression = "zstd -Xcompression-level 6";
44 isoImage.makeEfiBootable = true;
45 isoImage.makeUsbBootable = true;
46 image.baseName = lib.mkForce "atlas";
47 image.fileName = lib.mkForce "atlas.iso";
48 };
49
50 # PXE/netboot: iPXE pulls only bzImage + a ~11 MB initrd, so nothing has to
51 # be flashed to iterate.
52 #
53 # The store is NOT embedded in the initrd the way upstream netboot.nix does
54 # it. iPXE has to hold whatever it downloads in EfiBootServicesData, and
55 # firmware caps that well below installed RAM — a 2.8 GB initrd died with
56 # "No space left on device" (ipxe.org/34182006) on a 16 GB machine. So stage
57 # 1 brings up the network itself and fetches the squashfs, which lands in
58 # kernel-managed memory where the whole 16 GB is actually usable.
59 #
60 # The file is fetched to exactly the path netboot.nix already mounts from
61 # ("../nix-store.squashfs", i.e. /nix-store.squashfs in the initrd root), so
62 # the mount itself is upstream's, unmodified. It survives switch_root
63 # because the loop device keeps the inode open after the old root is wiped.
64 image.modules.netboot =
65 { config, lib, ... }:
66 {
67 imports = [ (modulesPath + "/installer/netboot/netboot.nix") ];
68 netboot.squashfsCompression = "zstd -Xcompression-level 6";
69
70 # Ship the initrd without the store; pxe-serve serves squashfsStore
71 # separately over HTTP.
72 system.build.netbootRamdisk = lib.mkForce config.system.build.initialRamdisk;
73
74 # DHCP in stage 1 (runs in preLVMCommands, before postDeviceCommands).
75 boot.initrd.network.enable = true;
76 boot.initrd.network.udhcpc.enable = true;
77
78 # hardware.enableAllHardware is storage-only — virtio_net is the single
79 # network driver in its 89 modules — so wired NICs have to be listed by
80 # hand. availableKernelModules only makes them available; udev loads just
81 # the ones matching present PCI/USB IDs, so unused entries cost bytes.
82 boot.initrd.availableKernelModules = [
83 "e1000e"
84 "e1000"
85 "igb"
86 "igc"
87 "ixgbe"
88 "r8169"
89 "alx"
90 "atl1c"
91 "tg3"
92 "bnx2"
93 "bnx2x"
94 "sky2"
95 "forcedeth"
96 "atlantic"
97 # USB NICs (docks, adapters)
98 "r8152"
99 "cdc_ether"
100 "cdc_ncm"
101 "asix"
102 "ax88179_178a"
103 "virtio_net"
104 ];
105
106 # wget is busybox's, already present in the scripted initrd's
107 # extra-utils. The URL comes from the kernel command line rather than
108 # being baked in, so the image doesn't go stale when the serving address
109 # changes (pxe-serve passes store.url=).
110 boot.initrd.postDeviceCommands = ''
111 store_url=
112 for o in $(cat /proc/cmdline); do
113 case $o in
114 store.url=*) store_url=''${o#store.url=} ;;
115 esac
116 done
117 if [ -z "$store_url" ]; then
118 echo "netboot: no store.url= on the kernel command line"
119 fail
120 fi
121 echo "netboot: fetching nix store from $store_url"
122 if ! wget -O /nix-store.squashfs "$store_url"; then
123 echo "netboot: failed to fetch $store_url"
124 fail
125 fi
126 '';
127 };
128
129 nixpkgs.hostPlatform = "x86_64-linux";
130
131 boot.kernelPackages = pkgs.linuxPackages_latest;
132
133 # AMD Radeon (Navi 14 / RX 5500-class, PCI 1002:7340) -> amdgpu.
134 #
135 # amdgpu is deliberately NOT in boot.initrd.kernelModules: early-loading it
136 # tears down efifb before the GPU firmware is available in stage-1, which
137 # black-screens this card from the moment the kernel starts (measured: ~5min
138 # to reach systemd). Let udev load amdgpu in stage-2, where the firmware is
139 # present. hardware.amdgpu.initrd.enable is the same trap — it only adds the
140 # module and handles no firmware.
141 hardware.graphics.enable = true;
142 hardware.enableRedistributableFirmware = true;
143
144 # No 32-bit graphics stack: it drags in a second mesa plus a second LLVM
145 # (~840 MB uncompressed) and is only needed for Steam/Wine, which this
146 # stateless box doesn't run. Both image variants pay for it otherwise, and
147 # the netboot initrd has to fit under iPXE's sub-4GB allocation limit.
148 hardware.graphics.enable32Bit = false;
149
150 # nixpkgs' graphical-desktop.nix turns on speech-dispatcher for any
151 # graphical session, which pulls espeak and 676 MB of mbrola TTS voices.
152 # Nothing here speaks.
153 services.speechd.enable = lib.mkForce false;
154 services.xserver.videoDrivers = [ "amdgpu" ];
155 boot.kernelModules = [ "kvm-intel" ];
156
157 # Boot the live image off a USB mass-storage device. (Inert for netboot,
158 # where iPXE has already fetched kernel+initrd before the kernel starts.)
159 boot.initrd.availableKernelModules = [
160 "xhci_pci"
161 "ehci_pci"
162 "ahci"
163 "usb_storage"
164 "uas"
165 "usbhid"
166 "sd_mod"
167 ];
168
169 # Initrd size dominates boot for both media: GRUB reads it off USB at
170 # roughly 1 MB/s (measured: 59s loader phase vs 6.5s kernel + 4.5s initrd +
171 # 10s userspace), and for netboot it travels with the store image.
172 #
173 # It was 29 MB compressed / 83 MB unpacked, of which only 1.8 MB was kernel
174 # modules — the rest was the systemd-initrd closure (openssl, lvm2,
175 # cryptsetup, tpm2-tss, curl, krb5, bash-interactive). Stage 1 only has to
176 # find the boot medium and mount squashfs+overlay: no LUKS, no LVM, no TPM,
177 # no network. The scripted initrd drops that closure: 29 MB -> 11 MB.
178 boot.initrd.systemd.enable = lib.mkForce false;
179 boot.initrd.systemd.emergencyAccess = lib.mkForce false;
180 boot.initrd.services.lvm.enable = lib.mkForce false;
181
182 # Name the onboard Intel NIC deterministically.
183 services.udev.extraRules = ''
184 SUBSYSTEM=="net", ATTR{address}=="70:85:c2:0e:ea:3c", NAME="et0"
185 '';
186
187 # Stateless box: no clan secrets are deployed, so don't try to load an
188 # openssh host key from a vars path that will never exist on the live
189 # system. Let sshd generate an ephemeral key at boot instead.
190 services.openssh.hostKeys = lib.mkForce [
191 {
192 path = "/etc/ssh/ssh_host_ed25519_key";
193 type = "ed25519";
194 }
195 ];
196}