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