1#! @perl@
2
3use strict;
4use Cwd 'abs_path';
5use File::Spec;
6use File::Path;
7use File::Basename;
8use File::Slurp;
9use File::stat;
10
11umask(0022);
12
13sub uniq {
14 my %seen;
15 my @res = ();
16 foreach my $s (@_) {
17 if (!defined $seen{$s}) {
18 $seen{$s} = 1;
19 push @res, $s;
20 }
21 }
22 return @res;
23}
24
25sub runCommand {
26 my ($cmd) = @_;
27 open FILE, "$cmd 2>&1 |" or die "Failed to execute: $cmd\n";
28 my @ret = <FILE>;
29 close FILE;
30 return ($?, @ret);
31}
32
33# Process the command line.
34my $outDir = "/etc/nixos";
35my $rootDir = ""; # = /
36my $force = 0;
37my $noFilesystems = 0;
38my $showHardwareConfig = 0;
39
40for (my $n = 0; $n < scalar @ARGV; $n++) {
41 my $arg = $ARGV[$n];
42 if ($arg eq "--help") {
43 exec "man nixos-generate-config" or die;
44 }
45 elsif ($arg eq "--dir") {
46 $n++;
47 $outDir = $ARGV[$n];
48 die "$0: ‘--dir’ requires an argument\n" unless defined $outDir;
49 }
50 elsif ($arg eq "--root") {
51 $n++;
52 $rootDir = $ARGV[$n];
53 die "$0: ‘--root’ requires an argument\n" unless defined $rootDir;
54 $rootDir =~ s/\/*$//; # remove trailing slashes
55 }
56 elsif ($arg eq "--force") {
57 $force = 1;
58 }
59 elsif ($arg eq "--no-filesystems") {
60 $noFilesystems = 1;
61 }
62 elsif ($arg eq "--show-hardware-config") {
63 $showHardwareConfig = 1;
64 }
65 else {
66 die "$0: unrecognized argument ‘$arg’\n";
67 }
68}
69
70
71my @attrs = ();
72my @kernelModules = ();
73my @initrdKernelModules = ();
74my @initrdAvailableKernelModules = ();
75my @modulePackages = ();
76my @imports;
77
78
79sub debug {
80 return unless defined $ENV{"DEBUG"};
81 print STDERR @_;
82}
83
84
85my $cpuinfo = read_file "/proc/cpuinfo";
86
87
88sub hasCPUFeature {
89 my $feature = shift;
90 return $cpuinfo =~ /^flags\s*:.* $feature( |$)/m;
91}
92
93
94
95# Determine CPU governor to use
96if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
97 my $governors = read_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors");
98 # ondemand governor is not available on sandy bridge or later Intel CPUs
99 my @desired_governors = ("ondemand", "powersave");
100 my $e;
101
102 foreach $e (@desired_governors) {
103 if (index($governors, $e) != -1) {
104 last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";");
105 }
106 }
107}
108
109
110# Virtualization support?
111push @kernelModules, "kvm-intel" if hasCPUFeature "vmx";
112push @kernelModules, "kvm-amd" if hasCPUFeature "svm";
113
114
115# Look at the PCI devices and add necessary modules. Note that most
116# modules are auto-detected so we don't need to list them here.
117# However, some are needed in the initrd to boot the system.
118
119my $videoDriver;
120
121sub pciCheck {
122 my $path = shift;
123 my $vendor = read_file "$path/vendor"; chomp $vendor;
124 my $device = read_file "$path/device"; chomp $device;
125 my $class = read_file "$path/class"; chomp $class;
126
127 my $module;
128 if (-e "$path/driver/module") {
129 $module = basename `readlink -f $path/driver/module`;
130 chomp $module;
131 }
132
133 debug "$path: $vendor $device $class";
134 debug " $module" if defined $module;
135 debug "\n";
136
137 if (defined $module) {
138 # See the bottom of http://pciids.sourceforge.net/pci.ids for
139 # device classes.
140 if (# Mass-storage controller. Definitely important.
141 $class =~ /^0x01/ ||
142
143 # Firewire controller. A disk might be attached.
144 $class =~ /^0x0c00/ ||
145
146 # USB controller. Needed if we want to use the
147 # keyboard when things go wrong in the initrd.
148 $class =~ /^0x0c03/
149 )
150 {
151 push @initrdAvailableKernelModules, $module;
152 }
153 }
154
155 # broadcom STA driver (wl.ko)
156 # list taken from http://www.broadcom.com/docs/linux_sta/README.txt
157 if ($vendor eq "0x14e4" &&
158 ($device eq "0x4311" || $device eq "0x4312" || $device eq "0x4313" ||
159 $device eq "0x4315" || $device eq "0x4327" || $device eq "0x4328" ||
160 $device eq "0x4329" || $device eq "0x432a" || $device eq "0x432b" ||
161 $device eq "0x432c" || $device eq "0x432d" || $device eq "0x4353" ||
162 $device eq "0x4357" || $device eq "0x4358" || $device eq "0x4359" ||
163 $device eq "0x4331" || $device eq "0x43a0" || $device eq "0x43b1"
164 ) )
165 {
166 push @modulePackages, "config.boot.kernelPackages.broadcom_sta";
167 push @kernelModules, "wl";
168 }
169
170 # broadcom FullMac driver
171 # list taken from
172 # https://wireless.wiki.kernel.org/en/users/Drivers/brcm80211#brcmfmac
173 if ($vendor eq "0x14e4" &&
174 ($device eq "0x43a3" || $device eq "0x43df" || $device eq "0x43ec" ||
175 $device eq "0x43d3" || $device eq "0x43d9" || $device eq "0x43e9" ||
176 $device eq "0x43ba" || $device eq "0x43bb" || $device eq "0x43bc" ||
177 $device eq "0xaa52" || $device eq "0x43ca" || $device eq "0x43cb" ||
178 $device eq "0x43cc" || $device eq "0x43c3" || $device eq "0x43c4" ||
179 $device eq "0x43c5"
180 ) )
181 {
182 # we need e.g. brcmfmac43602-pcie.bin
183 push @imports, "(modulesPath + \"/hardware/network/broadcom-43xx.nix\")";
184 }
185
186 # In case this is a virtio scsi device, we need to explicitly make this available.
187 if ($vendor eq "0x1af4" && $device eq "0x1004") {
188 push @initrdAvailableKernelModules, "virtio_scsi";
189 }
190
191 # Can't rely on $module here, since the module may not be loaded
192 # due to missing firmware. Ideally we would check modules.pcimap
193 # here.
194 push @attrs, "networking.enableIntel2200BGFirmware = true;" if
195 $vendor eq "0x8086" &&
196 ($device eq "0x1043" || $device eq "0x104f" || $device eq "0x4220" ||
197 $device eq "0x4221" || $device eq "0x4223" || $device eq "0x4224");
198
199 push @attrs, "networking.enableIntel3945ABGFirmware = true;" if
200 $vendor eq "0x8086" &&
201 ($device eq "0x4229" || $device eq "0x4230" ||
202 $device eq "0x4222" || $device eq "0x4227");
203
204 # Assume that all NVIDIA cards are supported by the NVIDIA driver.
205 # There may be exceptions (e.g. old cards).
206 # FIXME: do we want to enable an unfree driver here?
207 #$videoDriver = "nvidia" if $vendor eq "0x10de" && $class =~ /^0x03/;
208}
209
210foreach my $path (glob "/sys/bus/pci/devices/*") {
211 pciCheck $path;
212}
213
214# Idem for USB devices.
215
216sub usbCheck {
217 my $path = shift;
218 my $class = read_file "$path/bInterfaceClass"; chomp $class;
219 my $subclass = read_file "$path/bInterfaceSubClass"; chomp $subclass;
220 my $protocol = read_file "$path/bInterfaceProtocol"; chomp $protocol;
221
222 my $module;
223 if (-e "$path/driver/module") {
224 $module = basename `readlink -f $path/driver/module`;
225 chomp $module;
226 }
227
228 debug "$path: $class $subclass $protocol";
229 debug " $module" if defined $module;
230 debug "\n";
231
232 if (defined $module) {
233 if (# Mass-storage controller. Definitely important.
234 $class eq "08" ||
235
236 # Keyboard. Needed if we want to use the
237 # keyboard when things go wrong in the initrd.
238 ($class eq "03" && $protocol eq "01")
239 )
240 {
241 push @initrdAvailableKernelModules, $module;
242 }
243 }
244}
245
246foreach my $path (glob "/sys/bus/usb/devices/*") {
247 if (-e "$path/bInterfaceClass") {
248 usbCheck $path;
249 }
250}
251
252
253# Add the modules for all block and MMC devices.
254foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
255 my $module;
256 if (-e "$path/device/driver/module") {
257 $module = basename `readlink -f $path/device/driver/module`;
258 chomp $module;
259 push @initrdAvailableKernelModules, $module;
260 }
261}
262
263# Add bcache module, if needed.
264my @bcacheDevices = glob("/dev/bcache*");
265if (scalar @bcacheDevices > 0) {
266 push @initrdAvailableKernelModules, "bcache";
267}
268
269# Prevent unbootable systems if LVM snapshots are present at boot time.
270if (`lsblk -o TYPE` =~ "lvm") {
271 push @initrdKernelModules, "dm-snapshot";
272}
273
274my $virt = `systemd-detect-virt`;
275chomp $virt;
276
277
278# Check if we're a VirtualBox guest. If so, enable the guest
279# additions.
280if ($virt eq "oracle") {
281 push @attrs, "virtualisation.virtualbox.guest.enable = true;"
282}
283
284
285# Likewise for QEMU.
286if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
287 push @imports, "(modulesPath + \"/profiles/qemu-guest.nix\")";
288}
289
290# Also for Hyper-V.
291if ($virt eq "microsoft") {
292 push @attrs, "virtualisation.hypervGuest.enable = true;"
293}
294
295
296# Pull in NixOS configuration for containers.
297if ($virt eq "systemd-nspawn") {
298 push @attrs, "boot.isContainer = true;";
299}
300
301
302# Provide firmware for devices that are not detected by this script,
303# unless we're in a VM/container.
304push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")"
305 if $virt eq "none";
306
307
308# For a device name like /dev/sda1, find a more stable path like
309# /dev/disk/by-uuid/X or /dev/disk/by-label/Y.
310sub findStableDevPath {
311 my ($dev) = @_;
312 return $dev if substr($dev, 0, 1) ne "/";
313 return $dev unless -e $dev;
314
315 my $st = stat($dev) or return $dev;
316
317 foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
318 my $st2 = stat($dev2) or next;
319 return $dev2 if $st->rdev == $st2->rdev;
320 }
321
322 return $dev;
323}
324
325push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDriver;
326
327# Generate the swapDevices option from the currently activated swap
328# devices.
329my @swaps = read_file("/proc/swaps", err_mode => 'carp');
330my @swapDevices;
331if (@swaps) {
332 shift @swaps;
333 foreach my $swap (@swaps) {
334 my @fields = split ' ', $swap;
335 my $swapFilename = $fields[0];
336 my $swapType = $fields[1];
337 next unless -e $swapFilename;
338 my $dev = findStableDevPath $swapFilename;
339 if ($swapType =~ "partition") {
340 # zram devices are more likely created by configuration.nix, so
341 # ignore them here
342 next if ($swapFilename =~ /^\/dev\/zram/);
343 push @swapDevices, "{ device = \"$dev\"; }";
344 } elsif ($swapType =~ "file") {
345 # swap *files* are more likely specified in configuration.nix, so
346 # ignore them here.
347 } else {
348 die "Unsupported swap type: $swapType\n";
349 }
350 }
351}
352
353
354# Generate the fileSystems option from the currently mounted
355# filesystems.
356sub in {
357 my ($d1, $d2) = @_;
358 return $d1 eq $d2 || substr($d1, 0, length($d2) + 1) eq "$d2/";
359}
360
361my $fileSystems;
362my %fsByDev;
363foreach my $fs (read_file("/proc/self/mountinfo")) {
364 chomp $fs;
365 my @fields = split / /, $fs;
366 my $mountPoint = $fields[4];
367 $mountPoint =~ s/\\040/ /g; # account for mount points with spaces in the name (\040 is the escape character)
368 $mountPoint =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
369 next unless -d $mountPoint;
370 my @mountOptions = split /,/, $fields[5];
371
372 next if !in($mountPoint, $rootDir);
373 $mountPoint = substr($mountPoint, length($rootDir)); # strip the root directory (e.g. /mnt)
374 $mountPoint = "/" if $mountPoint eq "";
375
376 # Skip special filesystems.
377 next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs";
378
379 # Skip the optional fields.
380 my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
381 my $fsType = $fields[$n];
382 my $device = $fields[$n + 1];
383 my @superOptions = split /,/, $fields[$n + 2];
384 $device =~ s/\\040/ /g; # account for devices with spaces in the name (\040 is the escape character)
385 $device =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
386
387 # Skip the read-only bind-mount on /nix/store.
388 next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions);
389
390 # Maybe this is a bind-mount of a filesystem we saw earlier?
391 if (defined $fsByDev{$fields[2]}) {
392 # Make sure this isn't a btrfs subvolume.
393 my $msg = `btrfs subvol show $rootDir$mountPoint`;
394 if ($? != 0 || $msg =~ /ERROR:/s) {
395 my $path = $fields[3]; $path = "" if $path eq "/";
396 my $base = $fsByDev{$fields[2]};
397 $base = "" if $base eq "/";
398 $fileSystems .= <<EOF;
399 fileSystems.\"$mountPoint\" =
400 { device = \"$base$path\";
401 fsType = \"none\";
402 options = \[ \"bind\" \];
403 };
404
405EOF
406 next;
407 }
408 }
409 $fsByDev{$fields[2]} = $mountPoint;
410
411 # We don't know how to handle FUSE filesystems.
412 if ($fsType eq "fuseblk" || $fsType eq "fuse") {
413 print STDERR "warning: don't know how to emit ‘fileSystem’ option for FUSE filesystem ‘$mountPoint’\n";
414 next;
415 }
416
417 # Is this a mount of a loopback device?
418 my @extraOptions;
419 if ($device =~ /\/dev\/loop(\d+)/) {
420 my $loopnr = $1;
421 my $backer = read_file "/sys/block/loop$loopnr/loop/backing_file";
422 if (defined $backer) {
423 chomp $backer;
424 $device = $backer;
425 push @extraOptions, "loop";
426 }
427 }
428
429 # Is this a btrfs filesystem?
430 if ($fsType eq "btrfs") {
431 my ($status, @info) = runCommand("btrfs subvol show $rootDir$mountPoint");
432 if ($status != 0 || join("", @info) =~ /ERROR:/) {
433 die "Failed to retrieve subvolume info for $mountPoint\n";
434 }
435 my @ids = join("\n", @info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
436 if ($#ids > 0) {
437 die "Btrfs subvol name for $mountPoint listed multiple times in mount\n"
438 } elsif ($#ids == 0) {
439 my @paths = join("", @info) =~ m/^([^\n]*)/;
440 if ($#paths > 0) {
441 die "Btrfs returned multiple paths for a single subvolume id, mountpoint $mountPoint\n";
442 } elsif ($#paths != 0) {
443 die "Btrfs did not return a path for the subvolume at $mountPoint\n";
444 }
445 push @extraOptions, "subvol=$paths[0]";
446 }
447 }
448
449 # Don't emit tmpfs entry for /tmp, because it most likely comes from the
450 # boot.tmpOnTmpfs option in configuration.nix (managed declaratively).
451 next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
452
453 # Emit the filesystem.
454 $fileSystems .= <<EOF;
455 fileSystems.\"$mountPoint\" =
456 { device = \"${\(findStableDevPath $device)}\";
457 fsType = \"$fsType\";
458EOF
459
460 if (scalar @extraOptions > 0) {
461 $fileSystems .= <<EOF;
462 options = \[ ${\join " ", map { "\"" . $_ . "\"" } uniq(@extraOptions)} \];
463EOF
464 }
465
466 $fileSystems .= <<EOF;
467 };
468
469EOF
470
471 # If this filesystem is on a LUKS device, then add a
472 # boot.initrd.luks.devices entry.
473 if (-e $device) {
474 my $deviceName = basename(abs_path($device));
475 if (-e "/sys/class/block/$deviceName"
476 && read_file("/sys/class/block/$deviceName/dm/uuid", err_mode => 'quiet') =~ /^CRYPT-LUKS/)
477 {
478 my @slaves = glob("/sys/class/block/$deviceName/slaves/*");
479 if (scalar @slaves == 1) {
480 my $slave = "/dev/" . basename($slaves[0]);
481 if (-e $slave) {
482 my $dmName = read_file("/sys/class/block/$deviceName/dm/name");
483 chomp $dmName;
484 # Ensure to add an entry only once
485 my $luksDevice = " boot.initrd.luks.devices.\"$dmName\".device";
486 if ($fileSystems !~ /^\Q$luksDevice\E/m) {
487 $fileSystems .= "$luksDevice = \"${\(findStableDevPath $slave)}\";\n\n";
488 }
489 }
490 }
491 }
492 }
493}
494
495# For lack of a better way to determine it, guess whether we should use a
496# bigger font for the console from the display mode on the first
497# framebuffer. A way based on the physical size/actual DPI reported by
498# the monitor would be nice, but I don't know how to do this without X :)
499my $fb_modes_file = "/sys/class/graphics/fb0/modes";
500if (-f $fb_modes_file && -r $fb_modes_file) {
501 my $modes = read_file($fb_modes_file);
502 $modes =~ m/([0-9]+)x([0-9]+)/;
503 my $console_width = $1, my $console_height = $2;
504 if ($console_width > 1920) {
505 push @attrs, "# high-resolution display";
506 push @attrs, 'hardware.video.hidpi.enable = lib.mkDefault true;';
507 }
508}
509
510
511# Generate the hardware configuration file.
512
513sub toNixStringList {
514 my $res = "";
515 foreach my $s (@_) {
516 $res .= " \"$s\"";
517 }
518 return $res;
519}
520sub toNixList {
521 my $res = "";
522 foreach my $s (@_) {
523 $res .= " $s";
524 }
525 return $res;
526}
527
528sub multiLineList {
529 my $indent = shift;
530 return " [ ]" if !@_;
531 my $res = "\n${indent}[ ";
532 my $first = 1;
533 foreach my $s (@_) {
534 $res .= "$indent " if !$first;
535 $first = 0;
536 $res .= "$s\n";
537 }
538 $res .= "$indent]";
539 return $res;
540}
541
542my $initrdAvailableKernelModules = toNixStringList(uniq @initrdAvailableKernelModules);
543my $initrdKernelModules = toNixStringList(uniq @initrdKernelModules);
544my $kernelModules = toNixStringList(uniq @kernelModules);
545my $modulePackages = toNixList(uniq @modulePackages);
546
547my $fsAndSwap = "";
548if (!$noFilesystems) {
549 $fsAndSwap = "\n$fileSystems ";
550 $fsAndSwap .= "swapDevices =" . multiLineList(" ", @swapDevices) . ";\n";
551}
552
553my $hwConfig = <<EOF;
554# Do not modify this file! It was generated by ‘nixos-generate-config’
555# and may be overwritten by future invocations. Please make changes
556# to /etc/nixos/configuration.nix instead.
557{ config, lib, pkgs, modulesPath, ... }:
558
559{
560 imports =${\multiLineList(" ", @imports)};
561
562 boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ];
563 boot.initrd.kernelModules = [$initrdKernelModules ];
564 boot.kernelModules = [$kernelModules ];
565 boot.extraModulePackages = [$modulePackages ];
566$fsAndSwap
567${\join "", (map { " $_\n" } (uniq @attrs))}}
568EOF
569
570sub generateNetworkingDhcpConfig {
571 my $config = <<EOF;
572 # The global useDHCP flag is deprecated, therefore explicitly set to false here.
573 # Per-interface useDHCP will be mandatory in the future, so this generated config
574 # replicates the default behaviour.
575 networking.useDHCP = false;
576EOF
577
578 foreach my $path (glob "/sys/class/net/*") {
579 my $dev = basename($path);
580 if ($dev ne "lo") {
581 $config .= " networking.interfaces.$dev.useDHCP = true;\n";
582 }
583 }
584
585 return $config;
586}
587
588sub generateXserverConfig {
589 my $xserverEnabled = "@xserverEnabled@";
590
591 my $config = "";
592 if ($xserverEnabled eq "1") {
593 $config = <<EOF;
594 # Enable the X11 windowing system.
595 services.xserver.enable = true;
596EOF
597 } else {
598 $config = <<EOF;
599 # Enable the X11 windowing system.
600 # services.xserver.enable = true;
601EOF
602 }
603}
604
605if ($showHardwareConfig) {
606 print STDOUT $hwConfig;
607} else {
608 $outDir = "$rootDir$outDir";
609
610 my $fn = "$outDir/hardware-configuration.nix";
611 print STDERR "writing $fn...\n";
612 mkpath($outDir, 0, 0755);
613 write_file($fn, $hwConfig);
614
615 # Generate a basic configuration.nix, unless one already exists.
616 $fn = "$outDir/configuration.nix";
617 if ($force || ! -e $fn) {
618 print STDERR "writing $fn...\n";
619
620 my $bootLoaderConfig = "";
621 if (-e "/sys/firmware/efi/efivars") {
622 $bootLoaderConfig = <<EOF;
623 # Use the systemd-boot EFI boot loader.
624 boot.loader.systemd-boot.enable = true;
625 boot.loader.efi.canTouchEfiVariables = true;
626EOF
627 } elsif (-e "/boot/extlinux") {
628 $bootLoaderConfig = <<EOF;
629 # Use the extlinux boot loader. (NixOS wants to enable GRUB by default)
630 boot.loader.grub.enable = false;
631 # Enables the generation of /boot/extlinux/extlinux.conf
632 boot.loader.generic-extlinux-compatible.enable = true;
633EOF
634 } elsif ($virt ne "systemd-nspawn") {
635 $bootLoaderConfig = <<EOF;
636 # Use the GRUB 2 boot loader.
637 boot.loader.grub.enable = true;
638 boot.loader.grub.version = 2;
639 # boot.loader.grub.efiSupport = true;
640 # boot.loader.grub.efiInstallAsRemovable = true;
641 # boot.loader.efi.efiSysMountPoint = "/boot/efi";
642 # Define on which hard drive you want to install Grub.
643 # boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
644EOF
645 }
646
647 my $networkingDhcpConfig = generateNetworkingDhcpConfig();
648
649 my $xserverConfig = generateXserverConfig();
650
651 (my $desktopConfiguration = <<EOF)=~s/^/ /gm;
652@desktopConfiguration@
653EOF
654
655 write_file($fn, <<EOF);
656@configuration@
657EOF
658 print STDERR "For more hardware-specific settings, see https://github.com/NixOS/nixos-hardware.\n"
659 } else {
660 print STDERR "warning: not overwriting existing $fn\n";
661 }
662}
663
664# workaround for a bug in substituteAll