17 if (!defined $seen{$s}) {
27 open FILE
, "$cmd 2>&1 |" or die "Failed to execute: $cmd\n";
33 # Process the command line.
34 my $outDir = "/etc/nixos";
35 my $rootDir = ""; # = /
37 my $noFilesystems = 0;
38 my $showHardwareConfig = 0;
40 for (my $n = 0; $n < scalar @ARGV; $n++) {
42 if ($arg eq "--help") {
43 exec "man nixos-generate-config" or die;
45 elsif ($arg eq "--dir") {
48 die "$0: ‘--dir’ requires an argument\n" unless defined $outDir;
50 elsif ($arg eq "--root") {
53 die "$0: ‘--root’ requires an argument\n" unless defined $rootDir;
54 die "$0: no need to specify `/` with `--root`, it is the default\n" if $rootDir eq "/";
55 $rootDir =~ s/\/*$//; # remove trailing slashes
56 $rootDir = File
::Spec
->rel2abs($rootDir); # resolve absolute path
58 elsif ($arg eq "--force") {
61 elsif ($arg eq "--no-filesystems") {
64 elsif ($arg eq "--show-hardware-config") {
65 $showHardwareConfig = 1;
68 die "$0: unrecognized argument ‘$arg’\n";
74 my @kernelModules = ();
75 my @initrdKernelModules = ();
76 my @initrdAvailableKernelModules = ();
77 my @modulePackages = ();
82 return unless defined $ENV{"DEBUG"};
88 push @attrs, "nixpkgs.hostPlatform = lib.mkDefault \"@hostPlatformSystem@\";";
91 my $cpuinfo = read_file
"/proc/cpuinfo";
96 return $cpuinfo =~ /^flags\s*:.* $feature( |$)/m;
100 sub cpuManufacturer
{
102 return $cpuinfo =~ /^vendor_id\s*:.* $id$/m;
105 # Virtualization support?
106 push @kernelModules, "kvm-intel" if hasCPUFeature
"vmx";
107 push @kernelModules, "kvm-amd" if hasCPUFeature
"svm";
110 # Look at the PCI devices and add necessary modules. Note that most
111 # modules are auto-detected so we don't need to list them here.
112 # However, some are needed in the initrd to boot the system.
118 my $vendor = read_file
"$path/vendor"; chomp $vendor;
119 my $device = read_file
"$path/device"; chomp $device;
120 my $class = read_file
"$path/class"; chomp $class;
123 if (-e
"$path/driver/module") {
124 $module = basename
`readlink -f $path/driver/module`;
128 debug
"$path: $vendor $device $class";
129 debug
" $module" if defined $module;
132 if (defined $module) {
133 # See the bottom of https://pciids.sourceforge.net/pci.ids for
135 if (# Mass-storage controller. Definitely important.
138 # Firewire controller. A disk might be attached.
139 $class =~ /^0x0c00/ ||
141 # USB controller. Needed if we want to use the
142 # keyboard when things go wrong in the initrd.
146 push @initrdAvailableKernelModules, $module;
150 # broadcom STA driver (wl.ko)
151 # list taken from http://www.broadcom.com/docs/linux_sta/README.txt
152 if ($vendor eq "0x14e4" &&
153 ($device eq "0x4311" || $device eq "0x4312" || $device eq "0x4313" ||
154 $device eq "0x4315" || $device eq "0x4327" || $device eq "0x4328" ||
155 $device eq "0x4329" || $device eq "0x432a" || $device eq "0x432b" ||
156 $device eq "0x432c" || $device eq "0x432d" || $device eq "0x4353" ||
157 $device eq "0x4357" || $device eq "0x4358" || $device eq "0x4359" ||
158 $device eq "0x4331" || $device eq "0x43a0" || $device eq "0x43b1"
161 push @modulePackages, "config.boot.kernelPackages.broadcom_sta";
162 push @kernelModules, "wl";
165 # broadcom FullMac driver
167 # https://wireless.wiki.kernel.org/en/users/Drivers/brcm80211#brcmfmac
168 if ($vendor eq "0x14e4" &&
169 ($device eq "0x43a3" || $device eq "0x43df" || $device eq "0x43ec" ||
170 $device eq "0x43d3" || $device eq "0x43d9" || $device eq "0x43e9" ||
171 $device eq "0x43ba" || $device eq "0x43bb" || $device eq "0x43bc" ||
172 $device eq "0xaa52" || $device eq "0x43ca" || $device eq "0x43cb" ||
173 $device eq "0x43cc" || $device eq "0x43c3" || $device eq "0x43c4" ||
177 # we need e.g. brcmfmac43602-pcie.bin
178 push @imports, "(modulesPath + \"/hardware/network/broadcom-43xx.nix\")";
181 # In case this is a virtio scsi device, we need to explicitly make this available.
182 if ($vendor eq "0x1af4" && ($device eq "0x1004" || $device eq "0x1048") ) {
183 push @initrdAvailableKernelModules, "virtio_scsi";
186 # Can't rely on $module here, since the module may not be loaded
187 # due to missing firmware. Ideally we would check modules.pcimap
189 push @attrs, "networking.enableIntel2200BGFirmware = true;" if
190 $vendor eq "0x8086" &&
191 ($device eq "0x1043" || $device eq "0x104f" || $device eq "0x4220" ||
192 $device eq "0x4221" || $device eq "0x4223" || $device eq "0x4224");
194 push @attrs, "networking.enableIntel3945ABGFirmware = true;" if
195 $vendor eq "0x8086" &&
196 ($device eq "0x4229" || $device eq "0x4230" ||
197 $device eq "0x4222" || $device eq "0x4227");
199 # Assume that all NVIDIA cards are supported by the NVIDIA driver.
200 # There may be exceptions (e.g. old cards).
201 # FIXME: do we want to enable an unfree driver here?
202 #$videoDriver = "nvidia" if $vendor eq "0x10de" && $class =~ /^0x03/;
205 foreach my $path (glob "/sys/bus/pci/devices/*") {
209 # Idem for USB devices.
213 my $class = read_file
"$path/bInterfaceClass"; chomp $class;
214 my $subclass = read_file
"$path/bInterfaceSubClass"; chomp $subclass;
215 my $protocol = read_file
"$path/bInterfaceProtocol"; chomp $protocol;
218 if (-e
"$path/driver/module") {
219 $module = basename
`readlink -f $path/driver/module`;
223 debug
"$path: $class $subclass $protocol";
224 debug
" $module" if defined $module;
227 if (defined $module) {
228 if (# Mass-storage controller. Definitely important.
231 # Keyboard. Needed if we want to use the
232 # keyboard when things go wrong in the initrd.
233 ($class eq "03" && $protocol eq "01")
236 push @initrdAvailableKernelModules, $module;
241 foreach my $path (glob "/sys/bus/usb/devices/*") {
242 if (-e
"$path/bInterfaceClass") {
248 # Add the modules for all block and MMC devices.
249 foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
251 if (-e
"$path/device/driver/module") {
252 $module = basename
`readlink -f $path/device/driver/module`;
254 push @initrdAvailableKernelModules, $module;
258 # Add bcache module, if needed.
259 my @bcacheDevices = glob("/dev/bcache*");
260 @bcacheDevices = grep(!m
#dev/bcachefs.*#, @bcacheDevices);
261 if (scalar @bcacheDevices > 0) {
262 push @initrdAvailableKernelModules, "bcache";
265 # Prevent unbootable systems if LVM snapshots are present at boot time.
266 if (`lsblk -o TYPE` =~ "lvm") {
267 push @initrdKernelModules, "dm-snapshot";
270 my $virt = `@detectvirt@`;
274 # Check if we're a VirtualBox guest. If so, enable the guest
276 if ($virt eq "oracle") {
277 push @attrs, "virtualisation.virtualbox.guest.enable = true;"
280 # Check if we're a Parallels guest. If so, enable the guest additions.
281 # It is blocked by https://github.com/systemd/systemd/pull/23859
282 if ($virt eq "parallels") {
283 push @attrs, "hardware.parallels.enable = true;";
284 push @attrs, "nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ \"prl-tools\" ];";
288 if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
289 push @imports, "(modulesPath + \"/profiles/qemu-guest.nix\")";
293 if ($virt eq "microsoft") {
294 push @attrs, "virtualisation.hypervGuest.enable = true;"
298 # Pull in NixOS configuration for containers.
299 if ($virt eq "systemd-nspawn") {
300 push @attrs, "boot.isContainer = true;";
304 # Check if we're on bare metal, not in a VM/container.
305 if ($virt eq "none") {
306 # Provide firmware for devices that are not detected by this script.
307 push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")";
309 # Update the microcode.
310 push @attrs, "hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer
"AuthenticAMD";
311 push @attrs, "hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer
"GenuineIntel";
314 # For a device name like /dev/sda1, find a more stable path like
315 # /dev/disk/by-uuid/X or /dev/disk/by-label/Y.
316 sub findStableDevPath
{
318 return $dev if substr($dev, 0, 1) ne "/";
319 return $dev unless -e
$dev;
321 my $st = stat($dev) or return $dev;
323 foreach my $dev2 (glob("/dev/stratis/*/*"), glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
324 my $st2 = stat($dev2) or next;
325 return $dev2 if $st->rdev == $st2->rdev;
331 push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDriver;
333 # Generate the swapDevices option from the currently activated swap
335 my @swaps = read_file
("/proc/swaps", err_mode
=> 'carp');
339 foreach my $swap (@swaps) {
340 my @fields = split ' ', $swap;
341 my $swapFilename = $fields[0];
342 my $swapType = $fields[1];
343 next unless -e
$swapFilename;
344 my $dev = findStableDevPath
$swapFilename;
345 if ($swapType =~ "partition") {
346 # zram devices are more likely created by configuration.nix, so
348 next if ($swapFilename =~ /^\/dev\
/zram/);
349 push @swapDevices, "{ device = \"$dev\"; }";
350 } elsif ($swapType =~ "file") {
351 # swap *files* are more likely specified in configuration.nix, so
354 die "Unsupported swap type: $swapType\n";
360 # Generate the fileSystems option from the currently mounted
364 return $d1 eq $d2 || substr($d1, 0, length($d2) + 1) eq "$d2/";
370 foreach my $fs (read_file
("/proc/self/mountinfo")) {
372 my @fields = split / /, $fs;
373 my $mountPoint = $fields[4];
374 $mountPoint =~ s/\\040/ /g; # account for mount points with spaces in the name (\040 is the escape character)
375 $mountPoint =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
376 next unless -d
$mountPoint;
377 my @mountOptions = split /,/, $fields[5];
379 next if !in($mountPoint, $rootDir);
380 $mountPoint = substr($mountPoint, length($rootDir)); # strip the root directory (e.g. /mnt)
381 $mountPoint = "/" if $mountPoint eq "";
383 # Skip special filesystems.
384 next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs";
386 # Skip the optional fields.
387 my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
388 my $fsType = $fields[$n];
389 my $device = $fields[$n + 1];
390 my @superOptions = split /,/, $fields[$n + 2];
391 $device =~ s/\\040/ /g; # account for devices with spaces in the name (\040 is the escape character)
392 $device =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
394 # Skip the read-only bind-mount on /nix/store.
395 next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions);
397 # Maybe this is a bind-mount of a filesystem we saw earlier?
398 if (defined $fsByDev{$fields[2]}) {
399 # Make sure this isn't a btrfs subvolume.
400 my $msg = `@btrfs@ subvol show $rootDir$mountPoint`;
401 if ($?
!= 0 || $msg =~ /ERROR:/s) {
402 my $path = $fields[3]; $path = "" if $path eq "/";
403 my $base = $fsByDev{$fields[2]};
404 $base = "" if $base eq "/";
405 $fileSystems .= <<EOF;
406 fileSystems.\"$mountPoint\" =
407 { device = \"$base$path\";
409 options = \[ \"bind\" \];
416 $fsByDev{$fields[2]} = $mountPoint;
418 # We don't know how to handle FUSE filesystems.
419 if ($fsType eq "fuseblk" || $fsType eq "fuse") {
420 print STDERR
"warning: don't know how to emit ‘fileSystem’ option for FUSE filesystem ‘$mountPoint’\n";
424 # Is this a mount of a loopback device?
426 if ($device =~ /\/dev\
/loop(\d+)/) {
428 my $backer = read_file
"/sys/block/loop$loopnr/loop/backing_file";
429 if (defined $backer) {
432 push @extraOptions, "loop";
436 # Is this a btrfs filesystem?
437 if ($fsType eq "btrfs") {
438 my ($status, @info) = runCommand
("@btrfs@ subvol show $rootDir$mountPoint");
439 if ($status != 0 || join("", @info) =~ /ERROR:/) {
440 die "Failed to retrieve subvolume info for $mountPoint\n";
442 my @ids = join("\n", @info) =~ m/^(?!\/\n).*Subvolume ID
:[ \t\n]*([0-9]+)/s
;
444 die "Btrfs subvol name for $mountPoint listed multiple times in mount\n"
445 } elsif ($#ids == 0) {
446 my @paths = join("", @info) =~ m/^([^\n]*)/;
448 die "Btrfs returned multiple paths for a single subvolume id, mountpoint $mountPoint\n";
449 } elsif ($#paths != 0) {
450 die "Btrfs did not return a path for the subvolume at $mountPoint\n";
452 push @extraOptions, "subvol=$paths[0]";
456 # Preserve umask (fmask, dmask) settings for vfat filesystems.
457 # (The default is to mount these world-readable, but that's a security risk
458 # for the EFI System Partition.)
459 if ($fsType eq "vfat") {
460 for (@superOptions) {
461 if ($_ =~ /fmask|dmask/) {
462 push @extraOptions, $_;
467 # is this a stratis fs?
468 my $stableDevPath = findStableDevPath
$device;
470 if ($stableDevPath =~ qr
#/dev/stratis/(.*)/.*#) {
472 my ($header, @lines) = split "\n", qx/stratis pool list/;
473 my $uuidIndex = index $header, 'UUID';
474 my ($line) = grep /^$poolName /, @lines;
475 $stratisPool = substr $line, $uuidIndex - 32, 36;
478 # Don't emit tmpfs entry for /tmp, because it most likely comes from the
479 # boot.tmp.useTmpfs option in configuration.nix (managed declaratively).
480 next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
482 # This should work for single and multi-device systems.
483 # still needs subvolume support
484 if ($fsType eq "bcachefs") {
485 my ($status, @info) = runCommand
("bcachefs fs usage $rootDir$mountPoint");
488 if ($status == 0 && $UUID =~ /^Filesystem:[ \t\n]*([0-9a-z-]+)/) {
489 $stableDevPath = "UUID=$1";
491 print STDERR
"warning: can't find bcachefs mount UUID falling back to device-path";
495 # Emit the filesystem.
496 $fileSystems .= <<EOF;
497 fileSystems.\"$mountPoint\" =
498 { device = \"$stableDevPath\";
499 fsType = \"$fsType\";
502 if (scalar @extraOptions > 0) {
503 $fileSystems .= <<EOF;
504 options = \[ ${\join " ", map { "\"" . $_ . "\"" } uniq(@extraOptions)} \];
509 $fileSystems .= <<EOF;
510 stratis.poolUuid = "$stratisPool";
514 $fileSystems .= <<EOF;
519 # If this filesystem is on a LUKS device, then add a
520 # boot.initrd.luks.devices entry.
522 my $deviceName = basename
(abs_path
($device));
523 my $dmUuid = read_file
("/sys/class/block/$deviceName/dm/uuid", err_mode
=> 'quiet');
524 if ($dmUuid =~ /^CRYPT-LUKS/)
526 my @slaves = glob("/sys/class/block/$deviceName/slaves/*");
527 if (scalar @slaves == 1) {
528 my $slave = "/dev/" . basename
($slaves[0]);
530 my $dmName = read_file
("/sys/class/block/$deviceName/dm/name");
532 # Ensure to add an entry only once
533 my $luksDevice = " boot.initrd.luks.devices.\"$dmName\".device";
534 if ($fileSystems !~ /^\Q$luksDevice\E/m) {
535 $fileSystems .= "$luksDevice = \"${\(findStableDevPath $slave)}\";\n\n";
540 if (-e
"/sys/class/block/$deviceName/md/uuid") {
546 push @attrs, "boot.swraid.enable = true;\n\n";
550 # Generate the hardware configuration file.
552 sub toNixStringList
{
569 return " [ ]" if !@_;
570 my $res = "\n${indent}[ ";
573 $res .= "$indent " if !$first;
581 my $initrdAvailableKernelModules = toNixStringList
(uniq
@initrdAvailableKernelModules);
582 my $initrdKernelModules = toNixStringList
(uniq
@initrdKernelModules);
583 my $kernelModules = toNixStringList
(uniq
@kernelModules);
584 my $modulePackages = toNixList
(uniq
@modulePackages);
587 if (!$noFilesystems) {
588 $fsAndSwap = "\n$fileSystems ";
589 $fsAndSwap .= "swapDevices =" . multiLineList
(" ", @swapDevices) . ";\n";
592 my $networkingDhcpConfig = generateNetworkingDhcpConfig
();
594 my $hwConfig = <<EOF;
595 # Do not modify this file! It was generated by ‘nixos-generate-config’
596 # and may be overwritten by future invocations. Please make changes
597 # to /etc/nixos/configuration.nix instead.
598 { config, lib, pkgs, modulesPath, ... }:
601 imports =${\multiLineList(" ", @imports)};
603 boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ];
604 boot.initrd.kernelModules = [$initrdKernelModules ];
605 boot.kernelModules = [$kernelModules ];
606 boot.extraModulePackages = [$modulePackages ];
608 $networkingDhcpConfig
609 ${\join "", (map { " $_\n" } (uniq @attrs))}}
612 sub generateNetworkingDhcpConfig
{
613 # FIXME disable networking.useDHCP by default when switching to networkd.
615 # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
616 # (the default) this is the recommended approach. When using systemd-networkd it's
617 # still possible to use this option, but it's recommended to use it in conjunction
618 # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
619 networking.useDHCP = lib.mkDefault true;
622 foreach my $path (glob "/sys/class/net/*") {
623 my $dev = basename
($path);
625 $config .= " # networking.interfaces.$dev.useDHCP = lib.mkDefault true;\n";
632 sub generateXserverConfig
{
633 my $xserverEnabled = "@xserverEnabled@";
636 if ($xserverEnabled eq "1") {
638 # Enable the X11 windowing system.
639 services.xserver.enable = true;
643 # Enable the X11 windowing system.
644 # services.xserver.enable = true;
649 if ($showHardwareConfig) {
650 print STDOUT
$hwConfig;
652 if ($outDir eq "/etc/nixos") {
653 $outDir = "$rootDir$outDir";
655 $outDir = File
::Spec
->rel2abs($outDir);
656 $outDir =~ s/\/*$//; # remove trailing slashes
659 my $fn = "$outDir/hardware-configuration.nix";
660 print STDERR
"writing $fn...\n";
661 mkpath
($outDir, 0, 0755);
662 write_file
($fn, $hwConfig);
664 # Generate a basic configuration.nix, unless one already exists.
665 $fn = "$outDir/configuration.nix";
666 if ($force || ! -e
$fn) {
667 print STDERR
"writing $fn...\n";
669 my $bootLoaderConfig = "";
670 if (-e
"/sys/firmware/efi/efivars") {
671 $bootLoaderConfig = <<EOF;
672 # Use the systemd-boot EFI boot loader.
673 boot.loader.systemd-boot.enable = true;
674 boot.loader.efi.canTouchEfiVariables = true;
676 } elsif (-e
"/boot/extlinux") {
677 $bootLoaderConfig = <<EOF;
678 # Use the extlinux boot loader. (NixOS wants to enable GRUB by default)
679 boot.loader.grub.enable = false;
680 # Enables the generation of /boot/extlinux/extlinux.conf
681 boot.loader.generic-extlinux-compatible.enable = true;
683 } elsif ($virt ne "systemd-nspawn") {
684 $bootLoaderConfig = <<EOF;
685 # Use the GRUB 2 boot loader.
686 boot.loader.grub.enable = true;
687 # boot.loader.grub.efiSupport = true;
688 # boot.loader.grub.efiInstallAsRemovable = true;
689 # boot.loader.efi.efiSysMountPoint = "/boot/efi";
690 # Define on which hard drive you want to install Grub.
691 # boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
695 my $networkingDhcpConfig = generateNetworkingDhcpConfig
();
697 my $xserverConfig = generateXserverConfig
();
699 (my $desktopConfiguration = <<EOF)=~s/^/ /gm;
700 @desktopConfiguration@
703 write_file
($fn, <<EOF);
706 print STDERR
"For more hardware-specific settings, see https://github.com/NixOS/nixos-hardware.\n"
708 print STDERR
"warning: not overwriting existing $fn\n";
712 # workaround for a bug in substituteAll