at 18.09-beta 20 kB view raw
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