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