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