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# Detect the number of CPU cores.
95my $cpus = scalar (grep {/^processor\s*:/} (split '\n', $cpuinfo));
96
97
98# Determine CPU governor to use
99if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
100 my $governors = read_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors");
101 # ondemand governor is not available on sandy bridge or later Intel CPUs
102 my @desired_governors = ("ondemand", "powersave");
103 my $e;
104
105 foreach $e (@desired_governors) {
106 if (index($governors, $e) != -1) {
107 last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";");
108 }
109 }
110}
111
112
113# Virtualization support?
114push @kernelModules, "kvm-intel" if hasCPUFeature "vmx";
115push @kernelModules, "kvm-amd" if hasCPUFeature "svm";
116
117
118# Look at the PCI devices and add necessary modules. Note that most
119# modules are auto-detected so we don't need to list them here.
120# However, some are needed in the initrd to boot the system.
121
122my $videoDriver;
123
124sub pciCheck {
125 my $path = shift;
126 my $vendor = read_file "$path/vendor"; chomp $vendor;
127 my $device = read_file "$path/device"; chomp $device;
128 my $class = read_file "$path/class"; chomp $class;
129
130 my $module;
131 if (-e "$path/driver/module") {
132 $module = basename `readlink -f $path/driver/module`;
133 chomp $module;
134 }
135
136 debug "$path: $vendor $device $class";
137 debug " $module" if defined $module;
138 debug "\n";
139
140 if (defined $module) {
141 # See the bottom of http://pciids.sourceforge.net/pci.ids for
142 # device classes.
143 if (# Mass-storage controller. Definitely important.
144 $class =~ /^0x01/ ||
145
146 # Firewire controller. A disk might be attached.
147 $class =~ /^0x0c00/ ||
148
149 # USB controller. Needed if we want to use the
150 # keyboard when things go wrong in the initrd.
151 $class =~ /^0x0c03/
152 )
153 {
154 push @initrdAvailableKernelModules, $module;
155 }
156 }
157
158 # broadcom STA driver (wl.ko)
159 # list taken from http://www.broadcom.com/docs/linux_sta/README.txt
160 if ($vendor eq "0x14e4" &&
161 ($device eq "0x4311" || $device eq "0x4312" || $device eq "0x4313" ||
162 $device eq "0x4315" || $device eq "0x4327" || $device eq "0x4328" ||
163 $device eq "0x4329" || $device eq "0x432a" || $device eq "0x432b" ||
164 $device eq "0x432c" || $device eq "0x432d" || $device eq "0x4353" ||
165 $device eq "0x4357" || $device eq "0x4358" || $device eq "0x4359" ||
166 $device eq "0x4331" || $device eq "0x43a0" || $device eq "0x43b1"
167 ) )
168 {
169 push @modulePackages, "config.boot.kernelPackages.broadcom_sta";
170 push @kernelModules, "wl";
171 }
172
173 # broadcom FullMac driver
174 # list taken from
175 # https://wireless.wiki.kernel.org/en/users/Drivers/brcm80211#brcmfmac
176 if ($vendor eq "0x14e4" &&
177 ($device eq "0x43a3" || $device eq "0x43df" || $device eq "0x43ec" ||
178 $device eq "0x43d3" || $device eq "0x43d9" || $device eq "0x43e9" ||
179 $device eq "0x43ba" || $device eq "0x43bb" || $device eq "0x43bc" ||
180 $device eq "0xaa52" || $device eq "0x43ca" || $device eq "0x43cb" ||
181 $device eq "0x43cc" || $device eq "0x43c3" || $device eq "0x43c4" ||
182 $device eq "0x43c5"
183 ) )
184 {
185 # we need e.g. brcmfmac43602-pcie.bin
186 push @imports, "<nixpkgs/nixos/modules/hardware/network/broadcom-43xx.nix>";
187 }
188
189 # Can't rely on $module here, since the module may not be loaded
190 # due to missing firmware. Ideally we would check modules.pcimap
191 # here.
192 push @attrs, "networking.enableIntel2200BGFirmware = true;" if
193 $vendor eq "0x8086" &&
194 ($device eq "0x1043" || $device eq "0x104f" || $device eq "0x4220" ||
195 $device eq "0x4221" || $device eq "0x4223" || $device eq "0x4224");
196
197 push @attrs, "networking.enableIntel3945ABGFirmware = true;" if
198 $vendor eq "0x8086" &&
199 ($device eq "0x4229" || $device eq "0x4230" ||
200 $device eq "0x4222" || $device eq "0x4227");
201
202 # Assume that all NVIDIA cards are supported by the NVIDIA driver.
203 # There may be exceptions (e.g. old cards).
204 # FIXME: do we want to enable an unfree driver here?
205 #$videoDriver = "nvidia" if $vendor eq "0x10de" && $class =~ /^0x03/;
206}
207
208foreach my $path (glob "/sys/bus/pci/devices/*") {
209 pciCheck $path;
210}
211
212# Idem for USB devices.
213
214sub usbCheck {
215 my $path = shift;
216 my $class = read_file "$path/bInterfaceClass"; chomp $class;
217 my $subclass = read_file "$path/bInterfaceSubClass"; chomp $subclass;
218 my $protocol = read_file "$path/bInterfaceProtocol"; chomp $protocol;
219
220 my $module;
221 if (-e "$path/driver/module") {
222 $module = basename `readlink -f $path/driver/module`;
223 chomp $module;
224 }
225
226 debug "$path: $class $subclass $protocol";
227 debug " $module" if defined $module;
228 debug "\n";
229
230 if (defined $module) {
231 if (# Mass-storage controller. Definitely important.
232 $class eq "08" ||
233
234 # Keyboard. Needed if we want to use the
235 # keyboard when things go wrong in the initrd.
236 ($class eq "03" && $protocol eq "01")
237 )
238 {
239 push @initrdAvailableKernelModules, $module;
240 }
241 }
242}
243
244foreach my $path (glob "/sys/bus/usb/devices/*") {
245 if (-e "$path/bInterfaceClass") {
246 usbCheck $path;
247 }
248}
249
250
251# Add the modules for all block and MMC devices.
252foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
253 my $module;
254 if (-e "$path/device/driver/module") {
255 $module = basename `readlink -f $path/device/driver/module`;
256 chomp $module;
257 push @initrdAvailableKernelModules, $module;
258 }
259}
260
261
262my $virt = `systemd-detect-virt`;
263chomp $virt;
264
265
266# Check if we're a VirtualBox guest. If so, enable the guest
267# additions.
268if ($virt eq "oracle") {
269 push @attrs, "virtualisation.virtualbox.guest.enable = true;"
270}
271
272
273# Likewise for QEMU.
274if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
275 push @imports, "<nixpkgs/nixos/modules/profiles/qemu-guest.nix>";
276}
277
278# Also for Hyper-V.
279if ($virt eq "microsoft") {
280 push @initrdAvailableKernelModules, "hv_storvsc";
281 $videoDriver = "fbdev";
282}
283
284
285# Pull in NixOS configuration for containers.
286if ($virt eq "systemd-nspawn") {
287 push @attrs, "boot.isContainer = true;";
288}
289
290
291# Provide firmware for devices that are not detected by this script,
292# unless we're in a VM/container.
293push @imports, "<nixpkgs/nixos/modules/installer/scan/not-detected.nix>"
294 if $virt eq "none";
295
296
297# For a device name like /dev/sda1, find a more stable path like
298# /dev/disk/by-uuid/X or /dev/disk/by-label/Y.
299sub findStableDevPath {
300 my ($dev) = @_;
301 return $dev if substr($dev, 0, 1) ne "/";
302 return $dev unless -e $dev;
303
304 my $st = stat($dev) or return $dev;
305
306 foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
307 my $st2 = stat($dev2) or next;
308 return $dev2 if $st->rdev == $st2->rdev;
309 }
310
311 return $dev;
312}
313
314push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDriver;
315
316# Generate the swapDevices option from the currently activated swap
317# devices.
318my @swaps = read_file("/proc/swaps");
319shift @swaps;
320my @swapDevices;
321foreach my $swap (@swaps) {
322 $swap =~ /^(\S+)\s/;
323 next unless -e $1;
324 my $dev = findStableDevPath $1;
325 push @swapDevices, "{ device = \"$dev\"; }";
326}
327
328
329# Generate the fileSystems option from the currently mounted
330# filesystems.
331sub in {
332 my ($d1, $d2) = @_;
333 return $d1 eq $d2 || substr($d1, 0, length($d2) + 1) eq "$d2/";
334}
335
336my $fileSystems;
337my %fsByDev;
338foreach my $fs (read_file("/proc/self/mountinfo")) {
339 chomp $fs;
340 my @fields = split / /, $fs;
341 my $mountPoint = $fields[4];
342 next unless -d $mountPoint;
343 my @mountOptions = split /,/, $fields[5];
344
345 next if !in($mountPoint, $rootDir);
346 $mountPoint = substr($mountPoint, length($rootDir)); # strip the root directory (e.g. /mnt)
347 $mountPoint = "/" if $mountPoint eq "";
348
349 # Skip special filesystems.
350 next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs";
351
352 # Skip the optional fields.
353 my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
354 my $fsType = $fields[$n];
355 my $device = $fields[$n + 1];
356 my @superOptions = split /,/, $fields[$n + 2];
357
358 # Skip the read-only bind-mount on /nix/store.
359 next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions);
360
361 # Maybe this is a bind-mount of a filesystem we saw earlier?
362 if (defined $fsByDev{$fields[2]}) {
363 # Make sure this isn't a btrfs subvolume.
364 my $msg = `btrfs subvol show $rootDir$mountPoint`;
365 if ($? != 0 || $msg =~ /ERROR:/s) {
366 my $path = $fields[3]; $path = "" if $path eq "/";
367 my $base = $fsByDev{$fields[2]};
368 $base = "" if $base eq "/";
369 $fileSystems .= <<EOF;
370 fileSystems.\"$mountPoint\" =
371 { device = \"$base$path\";
372 fsType = \"none\";
373 options = \[ \"bind\" \];
374 };
375
376EOF
377 next;
378 }
379 }
380 $fsByDev{$fields[2]} = $mountPoint;
381
382 # We don't know how to handle FUSE filesystems.
383 if ($fsType eq "fuseblk" || $fsType eq "fuse") {
384 print STDERR "warning: don't know how to emit ‘fileSystem’ option for FUSE filesystem ‘$mountPoint’\n";
385 next;
386 }
387
388 # Is this a mount of a loopback device?
389 my @extraOptions;
390 if ($device =~ /\/dev\/loop(\d+)/) {
391 my $loopnr = $1;
392 my $backer = read_file "/sys/block/loop$loopnr/loop/backing_file";
393 if (defined $backer) {
394 chomp $backer;
395 $device = $backer;
396 push @extraOptions, "loop";
397 }
398 }
399
400 # Is this a btrfs filesystem?
401 if ($fsType eq "btrfs") {
402 my ($status, @info) = runCommand("btrfs subvol show $rootDir$mountPoint");
403 if ($status != 0 || join("", @info) =~ /ERROR:/) {
404 die "Failed to retrieve subvolume info for $mountPoint\n";
405 }
406 my @ids = join("\n", @info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
407 if ($#ids > 0) {
408 die "Btrfs subvol name for $mountPoint listed multiple times in mount\n"
409 } elsif ($#ids == 0) {
410 my @paths = join("", @info) =~ m/^([^\n]*)/;
411 if ($#paths > 0) {
412 die "Btrfs returned multiple paths for a single subvolume id, mountpoint $mountPoint\n";
413 } elsif ($#paths != 0) {
414 die "Btrfs did not return a path for the subvolume at $mountPoint\n";
415 }
416 push @extraOptions, "subvol=$paths[0]";
417 }
418 }
419
420 # Emit the filesystem.
421 $fileSystems .= <<EOF;
422 fileSystems.\"$mountPoint\" =
423 { device = \"${\(findStableDevPath $device)}\";
424 fsType = \"$fsType\";
425EOF
426
427 if (scalar @extraOptions > 0) {
428 $fileSystems .= <<EOF;
429 options = \[ ${\join " ", map { "\"" . $_ . "\"" } uniq(@extraOptions)} \];
430EOF
431 }
432
433 $fileSystems .= <<EOF;
434 };
435
436EOF
437
438 # If this filesystem is on a LUKS device, then add a
439 # boot.initrd.luks.devices entry.
440 if (-e $device) {
441 my $deviceName = basename(abs_path($device));
442 if (-e "/sys/class/block/$deviceName"
443 && read_file("/sys/class/block/$deviceName/dm/uuid", err_mode => 'quiet') =~ /^CRYPT-LUKS/)
444 {
445 my @slaves = glob("/sys/class/block/$deviceName/slaves/*");
446 if (scalar @slaves == 1) {
447 my $slave = "/dev/" . basename($slaves[0]);
448 if (-e $slave) {
449 my $dmName = read_file("/sys/class/block/$deviceName/dm/name");
450 chomp $dmName;
451 $fileSystems .= " boot.initrd.luks.devices.\"$dmName\".device = \"${\(findStableDevPath $slave)}\";\n\n";
452 }
453 }
454 }
455 }
456}
457
458
459# Generate the hardware configuration file.
460
461sub toNixStringList {
462 my $res = "";
463 foreach my $s (@_) {
464 $res .= " \"$s\"";
465 }
466 return $res;
467}
468sub toNixList {
469 my $res = "";
470 foreach my $s (@_) {
471 $res .= " $s";
472 }
473 return $res;
474}
475
476sub multiLineList {
477 my $indent = shift;
478 return " [ ]" if !@_;
479 my $res = "\n${indent}[ ";
480 my $first = 1;
481 foreach my $s (@_) {
482 $res .= "$indent " if !$first;
483 $first = 0;
484 $res .= "$s\n";
485 }
486 $res .= "$indent]";
487 return $res;
488}
489
490my $initrdAvailableKernelModules = toNixStringList(uniq @initrdAvailableKernelModules);
491my $kernelModules = toNixStringList(uniq @kernelModules);
492my $modulePackages = toNixList(uniq @modulePackages);
493
494my $fsAndSwap = "";
495if (!$noFilesystems) {
496 $fsAndSwap = "\n$fileSystems ";
497 $fsAndSwap .= "swapDevices =" . multiLineList(" ", @swapDevices) . ";\n";
498}
499
500my $hwConfig = <<EOF;
501# Do not modify this file! It was generated by ‘nixos-generate-config’
502# and may be overwritten by future invocations. Please make changes
503# to /etc/nixos/configuration.nix instead.
504{ config, lib, pkgs, ... }:
505
506{
507 imports =${\multiLineList(" ", @imports)};
508
509 boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ];
510 boot.kernelModules = [$kernelModules ];
511 boot.extraModulePackages = [$modulePackages ];
512$fsAndSwap
513 nix.maxJobs = lib.mkDefault $cpus;
514${\join "", (map { " $_\n" } (uniq @attrs))}}
515EOF
516
517
518if ($showHardwareConfig) {
519 print STDOUT $hwConfig;
520} else {
521 $outDir = "$rootDir$outDir";
522
523 my $fn = "$outDir/hardware-configuration.nix";
524 print STDERR "writing $fn...\n";
525 mkpath($outDir, 0, 0755);
526 write_file($fn, $hwConfig);
527
528 # Generate a basic configuration.nix, unless one already exists.
529 $fn = "$outDir/configuration.nix";
530 if ($force || ! -e $fn) {
531 print STDERR "writing $fn...\n";
532
533 my $bootLoaderConfig = "";
534 if (-e "/sys/firmware/efi/efivars") {
535 $bootLoaderConfig = <<EOF;
536 # Use the systemd-boot EFI boot loader.
537 boot.loader.systemd-boot.enable = true;
538 boot.loader.efi.canTouchEfiVariables = true;
539EOF
540 } elsif (-e "/boot/extlinux") {
541 $bootLoaderConfig = <<EOF;
542 # Use the extlinux boot loader. (NixOS wants to enable GRUB by default)
543 boot.loader.grub.enable = false;
544 # Enables the generation of /boot/extlinux/extlinux.conf
545 boot.loader.generic-extlinux-compatible.enable = true;
546EOF
547 } elsif ($virt ne "systemd-nspawn") {
548 $bootLoaderConfig = <<EOF;
549 # Use the GRUB 2 boot loader.
550 boot.loader.grub.enable = true;
551 boot.loader.grub.version = 2;
552 # boot.loader.grub.efiSupport = true;
553 # boot.loader.grub.efiInstallAsRemovable = true;
554 # boot.loader.efi.efiSysMountPoint = "/boot/efi";
555 # Define on which hard drive you want to install Grub.
556 # boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
557EOF
558 }
559
560 write_file($fn, <<EOF);
561# Edit this configuration file to define what should be installed on
562# your system. Help is available in the configuration.nix(5) man page
563# and in the NixOS manual (accessible by running ‘nixos-help’).
564
565{ config, pkgs, ... }:
566
567{
568 imports =
569 [ # Include the results of the hardware scan.
570 ./hardware-configuration.nix
571 ];
572
573$bootLoaderConfig
574 # networking.hostName = "nixos"; # Define your hostname.
575 # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
576
577 # Configure network proxy if necessary
578 # networking.proxy.default = "http://user:password\@proxy:port/";
579 # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
580
581 # Select internationalisation properties.
582 # i18n = {
583 # consoleFont = "Lat2-Terminus16";
584 # consoleKeyMap = "us";
585 # defaultLocale = "en_US.UTF-8";
586 # };
587
588 # Set your time zone.
589 # time.timeZone = "Europe/Amsterdam";
590
591 # List packages installed in system profile. To search, run:
592 # \$ nix search wget
593 # environment.systemPackages = with pkgs; [
594 # wget vim
595 # ];
596
597 # Some programs need SUID wrappers, can be configured further or are
598 # started in user sessions.
599 # programs.mtr.enable = true;
600 # programs.gnupg.agent = { enable = true; enableSSHSupport = true; };
601
602 # List services that you want to enable:
603
604 # Enable the OpenSSH daemon.
605 # services.openssh.enable = true;
606
607 # Open ports in the firewall.
608 # networking.firewall.allowedTCPPorts = [ ... ];
609 # networking.firewall.allowedUDPPorts = [ ... ];
610 # Or disable the firewall altogether.
611 # networking.firewall.enable = false;
612
613 # Enable CUPS to print documents.
614 # services.printing.enable = true;
615
616 # Enable sound.
617 # sound.enable = true;
618 # hardware.pulseaudio.enable = true;
619
620 # Enable the X11 windowing system.
621 # services.xserver.enable = true;
622 # services.xserver.layout = "us";
623 # services.xserver.xkbOptions = "eurosign:e";
624
625 # Enable touchpad support.
626 # services.xserver.libinput.enable = true;
627
628 # Enable the KDE Desktop Environment.
629 # services.xserver.displayManager.sddm.enable = true;
630 # services.xserver.desktopManager.plasma5.enable = true;
631
632 # Define a user account. Don't forget to set a password with ‘passwd’.
633 # users.users.guest = {
634 # isNormalUser = true;
635 # uid = 1000;
636 # };
637
638 # This value determines the NixOS release with which your system is to be
639 # compatible, in order to avoid breaking some software such as database
640 # servers. You should change this only after NixOS release notes say you
641 # should.
642 system.stateVersion = "${\(qw(@release@))}"; # Did you read the comment?
643
644}
645EOF
646 } else {
647 print STDERR "warning: not overwriting existing $fn\n";
648 }
649}
650
651# workaround for a bug in substituteAll