at 17.09-beta 19 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 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