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 # Can't rely on $module here, since the module may not be loaded
156 # due to missing firmware. Ideally we would check modules.pcimap
157 # here.
158 push @attrs, "networking.enableIntel2200BGFirmware = true;" if
159 $vendor eq "0x8086" &&
160 ($device eq "0x1043" || $device eq "0x104f" || $device eq "0x4220" ||
161 $device eq "0x4221" || $device eq "0x4223" || $device eq "0x4224");
162
163 push @attrs, "networking.enableIntel3945ABGFirmware = true;" if
164 $vendor eq "0x8086" &&
165 ($device eq "0x4229" || $device eq "0x4230" ||
166 $device eq "0x4222" || $device eq "0x4227");
167
168 # Assume that all NVIDIA cards are supported by the NVIDIA driver.
169 # There may be exceptions (e.g. old cards).
170 # FIXME: do we want to enable an unfree driver here?
171 #$videoDriver = "nvidia" if $vendor eq "0x10de" && $class =~ /^0x03/;
172}
173
174foreach my $path (glob "/sys/bus/pci/devices/*") {
175 pciCheck $path;
176}
177
178push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDriver;
179
180
181# Idem for USB devices.
182
183sub usbCheck {
184 my $path = shift;
185 my $class = read_file "$path/bInterfaceClass"; chomp $class;
186 my $subclass = read_file "$path/bInterfaceSubClass"; chomp $subclass;
187 my $protocol = read_file "$path/bInterfaceProtocol"; chomp $protocol;
188
189 my $module;
190 if (-e "$path/driver/module") {
191 $module = basename `readlink -f $path/driver/module`;
192 chomp $module;
193 }
194
195 debug "$path: $class $subclass $protocol";
196 debug " $module" if defined $module;
197 debug "\n";
198
199 if (defined $module) {
200 if (# Mass-storage controller. Definitely important.
201 $class eq "08" ||
202
203 # Keyboard. Needed if we want to use the
204 # keyboard when things go wrong in the initrd.
205 ($class eq "03" && $protocol eq "01")
206 )
207 {
208 push @initrdAvailableKernelModules, $module;
209 }
210 }
211}
212
213foreach my $path (glob "/sys/bus/usb/devices/*") {
214 if (-e "$path/bInterfaceClass") {
215 usbCheck $path;
216 }
217}
218
219
220# Add the modules for all block devices.
221foreach my $path (glob "/sys/class/block/*") {
222 my $module;
223 if (-e "$path/device/driver/module") {
224 $module = basename `readlink -f $path/device/driver/module`;
225 chomp $module;
226 push @initrdAvailableKernelModules, $module;
227 }
228}
229
230
231my $virt = `systemd-detect-virt`;
232chomp $virt;
233
234
235# Check if we're a VirtualBox guest. If so, enable the guest
236# additions.
237if ($virt eq "oracle") {
238 push @attrs, "virtualisation.virtualbox.guest.enable = true;"
239}
240
241
242# Likewise for QEMU.
243if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
244 push @imports, "<nixpkgs/nixos/modules/profiles/qemu-guest.nix>";
245}
246
247
248# Pull in NixOS configuration for containers.
249if ($virt eq "systemd-nspawn") {
250 push @attrs, "boot.isContainer = true;";
251}
252
253
254# Provide firmware for devices that are not detected by this script,
255# unless we're in a VM/container.
256push @imports, "<nixpkgs/nixos/modules/installer/scan/not-detected.nix>"
257 if $virt eq "none";
258
259
260# For a device name like /dev/sda1, find a more stable path like
261# /dev/disk/by-uuid/X or /dev/disk/by-label/Y.
262sub findStableDevPath {
263 my ($dev) = @_;
264 return $dev if substr($dev, 0, 1) ne "/";
265 return $dev unless -e $dev;
266
267 my $st = stat($dev) or return $dev;
268
269 foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
270 my $st2 = stat($dev2) or next;
271 return $dev2 if $st->rdev == $st2->rdev;
272 }
273
274 return $dev;
275}
276
277
278# Generate the swapDevices option from the currently activated swap
279# devices.
280my @swaps = read_file("/proc/swaps");
281shift @swaps;
282my @swapDevices;
283foreach my $swap (@swaps) {
284 $swap =~ /^(\S+)\s/;
285 next unless -e $1;
286 my $dev = findStableDevPath $1;
287 push @swapDevices, "{ device = \"$dev\"; }";
288}
289
290
291# Generate the fileSystems option from the currently mounted
292# filesystems.
293sub in {
294 my ($d1, $d2) = @_;
295 return $d1 eq $d2 || substr($d1, 0, length($d2) + 1) eq "$d2/";
296}
297
298my $fileSystems;
299my %fsByDev;
300foreach my $fs (read_file("/proc/self/mountinfo")) {
301 chomp $fs;
302 my @fields = split / /, $fs;
303 my $mountPoint = $fields[4];
304 next unless -d $mountPoint;
305 my @mountOptions = split /,/, $fields[5];
306
307 next if !in($mountPoint, $rootDir);
308 $mountPoint = substr($mountPoint, length($rootDir)); # strip the root directory (e.g. /mnt)
309 $mountPoint = "/" if $mountPoint eq "";
310
311 # Skip special filesystems.
312 next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs";
313 next if $mountPoint eq "/var/setuid-wrappers";
314
315 # Skip the optional fields.
316 my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
317 my $fsType = $fields[$n];
318 my $device = $fields[$n + 1];
319 my @superOptions = split /,/, $fields[$n + 2];
320
321 # Skip the read-only bind-mount on /nix/store.
322 next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions);
323
324 # Maybe this is a bind-mount of a filesystem we saw earlier?
325 if (defined $fsByDev{$fields[2]}) {
326 # Make sure this isn't a btrfs subvolume.
327 my $msg = `btrfs subvol show $rootDir$mountPoint`;
328 if ($? != 0 || $msg =~ /ERROR:/s) {
329 my $path = $fields[3]; $path = "" if $path eq "/";
330 my $base = $fsByDev{$fields[2]};
331 $base = "" if $base eq "/";
332 $fileSystems .= <<EOF;
333 fileSystems.\"$mountPoint\" =
334 { device = \"$base$path\";
335 fsType = \"none\";
336 options = \"bind\";
337 };
338
339EOF
340 next;
341 }
342 }
343 $fsByDev{$fields[2]} = $mountPoint;
344
345 # We don't know how to handle FUSE filesystems.
346 if ($fsType eq "fuseblk" || $fsType eq "fuse") {
347 print STDERR "warning: don't know how to emit ‘fileSystem’ option for FUSE filesystem ‘$mountPoint’\n";
348 next;
349 }
350
351 # Is this a mount of a loopback device?
352 my @extraOptions;
353 if ($device =~ /\/dev\/loop(\d+)/) {
354 my $loopnr = $1;
355 my $backer = read_file "/sys/block/loop$loopnr/loop/backing_file";
356 if (defined $backer) {
357 chomp $backer;
358 $device = $backer;
359 push @extraOptions, "loop";
360 }
361 }
362
363 # Is this a btrfs filesystem?
364 if ($fsType eq "btrfs") {
365 my ($status, @id_info) = runCommand("btrfs subvol show $rootDir$mountPoint");
366 if ($status != 0 || join("", @msg) =~ /ERROR:/) {
367 die "Failed to retrieve subvolume info for $mountPoint\n";
368 }
369 my @ids = join("", @id_info) =~ m/Subvolume ID:[ \t\n]*([^ \t\n]*)/;
370 if ($#ids > 0) {
371 die "Btrfs subvol name for $mountPoint listed multiple times in mount\n"
372 } elsif ($#ids == 0) {
373 my ($status, @path_info) = runCommand("btrfs subvol list $rootDir$mountPoint");
374 if ($status != 0) {
375 die "Failed to find $mountPoint subvolume id from btrfs\n";
376 }
377 my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
378 if ($#paths > 0) {
379 die "Btrfs returned multiple paths for a single subvolume id, mountpoint $mountPoint\n";
380 } elsif ($#paths != 0) {
381 die "Btrfs did not return a path for the subvolume at $mountPoint\n";
382 }
383 push @extraOptions, "subvol=$paths[0]";
384 }
385 }
386
387 # Emit the filesystem.
388 $fileSystems .= <<EOF;
389 fileSystems.\"$mountPoint\" =
390 { device = \"${\(findStableDevPath $device)}\";
391 fsType = \"$fsType\";
392EOF
393
394 if (scalar @extraOptions > 0) {
395 $fileSystems .= <<EOF;
396 options = \"${\join ",", uniq(@extraOptions)}\";
397EOF
398 }
399
400 $fileSystems .= <<EOF;
401 };
402
403EOF
404}
405
406
407# Generate the hardware configuration file.
408
409sub toNixExpr {
410 my $res = "";
411 foreach my $s (@_) {
412 $res .= " \"$s\"";
413 }
414 return $res;
415}
416
417sub multiLineList {
418 my $indent = shift;
419 return " [ ]" if !@_;
420 $res = "\n${indent}[ ";
421 my $first = 1;
422 foreach my $s (@_) {
423 $res .= "$indent " if !$first;
424 $first = 0;
425 $res .= "$s\n";
426 }
427 $res .= "$indent]";
428 return $res;
429}
430
431my $initrdAvailableKernelModules = toNixExpr(uniq @initrdAvailableKernelModules);
432my $kernelModules = toNixExpr(uniq @kernelModules);
433my $modulePackages = toNixExpr(uniq @modulePackages);
434
435my $fsAndSwap = "";
436if (!$noFilesystems) {
437 $fsAndSwap = "\n${fileSystems} ";
438 $fsAndSwap .= "swapDevices =" . multiLineList(" ", @swapDevices) . ";\n";
439}
440
441my $hwConfig = <<EOF;
442# Do not modify this file! It was generated by ‘nixos-generate-config’
443# and may be overwritten by future invocations. Please make changes
444# to /etc/nixos/configuration.nix instead.
445{ config, lib, pkgs, ... }:
446
447{
448 imports =${\multiLineList(" ", @imports)};
449
450 boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ];
451 boot.kernelModules = [$kernelModules ];
452 boot.extraModulePackages = [$modulePackages ];
453$fsAndSwap
454 nix.maxJobs = $cpus;
455${\join "", (map { " $_\n" } (uniq @attrs))}}
456EOF
457
458
459if ($showHardwareConfig) {
460 print STDOUT $hwConfig;
461} else {
462 $outDir = "$rootDir$outDir";
463
464 my $fn = "$outDir/hardware-configuration.nix";
465 print STDERR "writing $fn...\n";
466 mkpath($outDir, 0, 0755);
467 write_file($fn, $hwConfig);
468
469 # Generate a basic configuration.nix, unless one already exists.
470 $fn = "$outDir/configuration.nix";
471 if ($force || ! -e $fn) {
472 print STDERR "writing $fn...\n";
473
474 my $bootloaderConfig = "";
475 if (-e "/sys/firmware/efi/efivars") {
476 $bootLoaderConfig = <<EOF;
477 # Use the gummiboot efi boot loader.
478 boot.loader.gummiboot.enable = true;
479 boot.loader.efi.canTouchEfiVariables = true;
480EOF
481 } elsif ($virt ne "systemd-nspawn") {
482 $bootLoaderConfig = <<EOF;
483 # Use the GRUB 2 boot loader.
484 boot.loader.grub.enable = true;
485 boot.loader.grub.version = 2;
486 # Define on which hard drive you want to install Grub.
487 # boot.loader.grub.device = "/dev/sda";
488EOF
489 }
490
491 write_file($fn, <<EOF);
492# Edit this configuration file to define what should be installed on
493# your system. Help is available in the configuration.nix(5) man page
494# and in the NixOS manual (accessible by running ‘nixos-help’).
495
496{ config, pkgs, ... }:
497
498{
499 imports =
500 [ # Include the results of the hardware scan.
501 ./hardware-configuration.nix
502 ];
503
504$bootLoaderConfig
505 # networking.hostName = "nixos"; # Define your hostname.
506 # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
507
508 # Select internationalisation properties.
509 # i18n = {
510 # consoleFont = "Lat2-Terminus16";
511 # consoleKeyMap = "us";
512 # defaultLocale = "en_US.UTF-8";
513 # };
514
515 # Set your time zone.
516 # time.timeZone = "Europe/Amsterdam";
517
518 # List packages installed in system profile. To search by name, run:
519 # \$ nix-env -qaP | grep wget
520 # environment.systemPackages = with pkgs; [
521 # wget
522 # ];
523
524 # List services that you want to enable:
525
526 # Enable the OpenSSH daemon.
527 # services.openssh.enable = true;
528
529 # Enable CUPS to print documents.
530 # services.printing.enable = true;
531
532 # Enable the X11 windowing system.
533 # services.xserver.enable = true;
534 # services.xserver.layout = "us";
535 # services.xserver.xkbOptions = "eurosign:e";
536
537 # Enable the KDE Desktop Environment.
538 # services.xserver.displayManager.kdm.enable = true;
539 # services.xserver.desktopManager.kde4.enable = true;
540
541 # Define a user account. Don't forget to set a password with ‘passwd’.
542 # users.extraUsers.guest = {
543 # isNormalUser = true;
544 # uid = 1000;
545 # };
546
547 # The NixOS release to be compatible with for stateful data such as databases.
548 system.stateVersion = "@nixosRelease@";
549
550}
551EOF
552 } else {
553 print STDERR "warning: not overwriting existing $fn\n";
554 }
555}
556
557# workaround for a bug in substituteAll