at 22.05-pre 21 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 94sub cpuManufacturer { 95 my $id = shift; 96 return $cpuinfo =~ /^vendor_id\s*:.* $id$/m; 97} 98 99 100# Determine CPU governor to use 101if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") { 102 my $governors = read_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"); 103 # ondemand governor is not available on sandy bridge or later Intel CPUs 104 my @desired_governors = ("ondemand", "powersave"); 105 my $e; 106 107 foreach $e (@desired_governors) { 108 if (index($governors, $e) != -1) { 109 last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";"); 110 } 111 } 112} 113 114 115# Virtualization support? 116push @kernelModules, "kvm-intel" if hasCPUFeature "vmx"; 117push @kernelModules, "kvm-amd" if hasCPUFeature "svm"; 118 119push @attrs, "hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "AuthenticAMD"; 120push @attrs, "hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "GenuineIntel"; 121 122 123# Look at the PCI devices and add necessary modules. Note that most 124# modules are auto-detected so we don't need to list them here. 125# However, some are needed in the initrd to boot the system. 126 127my $videoDriver; 128 129sub pciCheck { 130 my $path = shift; 131 my $vendor = read_file "$path/vendor"; chomp $vendor; 132 my $device = read_file "$path/device"; chomp $device; 133 my $class = read_file "$path/class"; chomp $class; 134 135 my $module; 136 if (-e "$path/driver/module") { 137 $module = basename `readlink -f $path/driver/module`; 138 chomp $module; 139 } 140 141 debug "$path: $vendor $device $class"; 142 debug " $module" if defined $module; 143 debug "\n"; 144 145 if (defined $module) { 146 # See the bottom of http://pciids.sourceforge.net/pci.ids for 147 # device classes. 148 if (# Mass-storage controller. Definitely important. 149 $class =~ /^0x01/ || 150 151 # Firewire controller. A disk might be attached. 152 $class =~ /^0x0c00/ || 153 154 # USB controller. Needed if we want to use the 155 # keyboard when things go wrong in the initrd. 156 $class =~ /^0x0c03/ 157 ) 158 { 159 push @initrdAvailableKernelModules, $module; 160 } 161 } 162 163 # broadcom STA driver (wl.ko) 164 # list taken from http://www.broadcom.com/docs/linux_sta/README.txt 165 if ($vendor eq "0x14e4" && 166 ($device eq "0x4311" || $device eq "0x4312" || $device eq "0x4313" || 167 $device eq "0x4315" || $device eq "0x4327" || $device eq "0x4328" || 168 $device eq "0x4329" || $device eq "0x432a" || $device eq "0x432b" || 169 $device eq "0x432c" || $device eq "0x432d" || $device eq "0x4353" || 170 $device eq "0x4357" || $device eq "0x4358" || $device eq "0x4359" || 171 $device eq "0x4331" || $device eq "0x43a0" || $device eq "0x43b1" 172 ) ) 173 { 174 push @modulePackages, "config.boot.kernelPackages.broadcom_sta"; 175 push @kernelModules, "wl"; 176 } 177 178 # broadcom FullMac driver 179 # list taken from 180 # https://wireless.wiki.kernel.org/en/users/Drivers/brcm80211#brcmfmac 181 if ($vendor eq "0x14e4" && 182 ($device eq "0x43a3" || $device eq "0x43df" || $device eq "0x43ec" || 183 $device eq "0x43d3" || $device eq "0x43d9" || $device eq "0x43e9" || 184 $device eq "0x43ba" || $device eq "0x43bb" || $device eq "0x43bc" || 185 $device eq "0xaa52" || $device eq "0x43ca" || $device eq "0x43cb" || 186 $device eq "0x43cc" || $device eq "0x43c3" || $device eq "0x43c4" || 187 $device eq "0x43c5" 188 ) ) 189 { 190 # we need e.g. brcmfmac43602-pcie.bin 191 push @imports, "(modulesPath + \"/hardware/network/broadcom-43xx.nix\")"; 192 } 193 194 # In case this is a virtio scsi device, we need to explicitly make this available. 195 if ($vendor eq "0x1af4" && $device eq "0x1004") { 196 push @initrdAvailableKernelModules, "virtio_scsi"; 197 } 198 199 # Can't rely on $module here, since the module may not be loaded 200 # due to missing firmware. Ideally we would check modules.pcimap 201 # here. 202 push @attrs, "networking.enableIntel2200BGFirmware = true;" if 203 $vendor eq "0x8086" && 204 ($device eq "0x1043" || $device eq "0x104f" || $device eq "0x4220" || 205 $device eq "0x4221" || $device eq "0x4223" || $device eq "0x4224"); 206 207 push @attrs, "networking.enableIntel3945ABGFirmware = true;" if 208 $vendor eq "0x8086" && 209 ($device eq "0x4229" || $device eq "0x4230" || 210 $device eq "0x4222" || $device eq "0x4227"); 211 212 # Assume that all NVIDIA cards are supported by the NVIDIA driver. 213 # There may be exceptions (e.g. old cards). 214 # FIXME: do we want to enable an unfree driver here? 215 #$videoDriver = "nvidia" if $vendor eq "0x10de" && $class =~ /^0x03/; 216} 217 218foreach my $path (glob "/sys/bus/pci/devices/*") { 219 pciCheck $path; 220} 221 222# Idem for USB devices. 223 224sub usbCheck { 225 my $path = shift; 226 my $class = read_file "$path/bInterfaceClass"; chomp $class; 227 my $subclass = read_file "$path/bInterfaceSubClass"; chomp $subclass; 228 my $protocol = read_file "$path/bInterfaceProtocol"; chomp $protocol; 229 230 my $module; 231 if (-e "$path/driver/module") { 232 $module = basename `readlink -f $path/driver/module`; 233 chomp $module; 234 } 235 236 debug "$path: $class $subclass $protocol"; 237 debug " $module" if defined $module; 238 debug "\n"; 239 240 if (defined $module) { 241 if (# Mass-storage controller. Definitely important. 242 $class eq "08" || 243 244 # Keyboard. Needed if we want to use the 245 # keyboard when things go wrong in the initrd. 246 ($class eq "03" && $protocol eq "01") 247 ) 248 { 249 push @initrdAvailableKernelModules, $module; 250 } 251 } 252} 253 254foreach my $path (glob "/sys/bus/usb/devices/*") { 255 if (-e "$path/bInterfaceClass") { 256 usbCheck $path; 257 } 258} 259 260 261# Add the modules for all block and MMC devices. 262foreach my $path (glob "/sys/class/{block,mmc_host}/*") { 263 my $module; 264 if (-e "$path/device/driver/module") { 265 $module = basename `readlink -f $path/device/driver/module`; 266 chomp $module; 267 push @initrdAvailableKernelModules, $module; 268 } 269} 270 271# Add bcache module, if needed. 272my @bcacheDevices = glob("/dev/bcache*"); 273if (scalar @bcacheDevices > 0) { 274 push @initrdAvailableKernelModules, "bcache"; 275} 276 277# Prevent unbootable systems if LVM snapshots are present at boot time. 278if (`lsblk -o TYPE` =~ "lvm") { 279 push @initrdKernelModules, "dm-snapshot"; 280} 281 282my $virt = `systemd-detect-virt`; 283chomp $virt; 284 285 286# Check if we're a VirtualBox guest. If so, enable the guest 287# additions. 288if ($virt eq "oracle") { 289 push @attrs, "virtualisation.virtualbox.guest.enable = true;" 290} 291 292 293# Likewise for QEMU. 294if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") { 295 push @imports, "(modulesPath + \"/profiles/qemu-guest.nix\")"; 296} 297 298# Also for Hyper-V. 299if ($virt eq "microsoft") { 300 push @attrs, "virtualisation.hypervGuest.enable = true;" 301} 302 303 304# Pull in NixOS configuration for containers. 305if ($virt eq "systemd-nspawn") { 306 push @attrs, "boot.isContainer = true;"; 307} 308 309 310# Provide firmware for devices that are not detected by this script, 311# unless we're in a VM/container. 312push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")" 313 if $virt eq "none"; 314 315 316# For a device name like /dev/sda1, find a more stable path like 317# /dev/disk/by-uuid/X or /dev/disk/by-label/Y. 318sub findStableDevPath { 319 my ($dev) = @_; 320 return $dev if substr($dev, 0, 1) ne "/"; 321 return $dev unless -e $dev; 322 323 my $st = stat($dev) or return $dev; 324 325 foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) { 326 my $st2 = stat($dev2) or next; 327 return $dev2 if $st->rdev == $st2->rdev; 328 } 329 330 return $dev; 331} 332 333push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDriver; 334 335# Generate the swapDevices option from the currently activated swap 336# devices. 337my @swaps = read_file("/proc/swaps", err_mode => 'carp'); 338my @swapDevices; 339if (@swaps) { 340 shift @swaps; 341 foreach my $swap (@swaps) { 342 my @fields = split ' ', $swap; 343 my $swapFilename = $fields[0]; 344 my $swapType = $fields[1]; 345 next unless -e $swapFilename; 346 my $dev = findStableDevPath $swapFilename; 347 if ($swapType =~ "partition") { 348 # zram devices are more likely created by configuration.nix, so 349 # ignore them here 350 next if ($swapFilename =~ /^\/dev\/zram/); 351 push @swapDevices, "{ device = \"$dev\"; }"; 352 } elsif ($swapType =~ "file") { 353 # swap *files* are more likely specified in configuration.nix, so 354 # ignore them here. 355 } else { 356 die "Unsupported swap type: $swapType\n"; 357 } 358 } 359} 360 361 362# Generate the fileSystems option from the currently mounted 363# filesystems. 364sub in { 365 my ($d1, $d2) = @_; 366 return $d1 eq $d2 || substr($d1, 0, length($d2) + 1) eq "$d2/"; 367} 368 369my $fileSystems; 370my %fsByDev; 371foreach my $fs (read_file("/proc/self/mountinfo")) { 372 chomp $fs; 373 my @fields = split / /, $fs; 374 my $mountPoint = $fields[4]; 375 $mountPoint =~ s/\\040/ /g; # account for mount points with spaces in the name (\040 is the escape character) 376 $mountPoint =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character) 377 next unless -d $mountPoint; 378 my @mountOptions = split /,/, $fields[5]; 379 380 next if !in($mountPoint, $rootDir); 381 $mountPoint = substr($mountPoint, length($rootDir)); # strip the root directory (e.g. /mnt) 382 $mountPoint = "/" if $mountPoint eq ""; 383 384 # Skip special filesystems. 385 next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs"; 386 387 # Skip the optional fields. 388 my $n = 6; $n++ while $fields[$n] ne "-"; $n++; 389 my $fsType = $fields[$n]; 390 my $device = $fields[$n + 1]; 391 my @superOptions = split /,/, $fields[$n + 2]; 392 $device =~ s/\\040/ /g; # account for devices with spaces in the name (\040 is the escape character) 393 $device =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character) 394 395 # Skip the read-only bind-mount on /nix/store. 396 next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions); 397 398 # Maybe this is a bind-mount of a filesystem we saw earlier? 399 if (defined $fsByDev{$fields[2]}) { 400 # Make sure this isn't a btrfs subvolume. 401 my $msg = `btrfs subvol show $rootDir$mountPoint`; 402 if ($? != 0 || $msg =~ /ERROR:/s) { 403 my $path = $fields[3]; $path = "" if $path eq "/"; 404 my $base = $fsByDev{$fields[2]}; 405 $base = "" if $base eq "/"; 406 $fileSystems .= <<EOF; 407 fileSystems.\"$mountPoint\" = 408 { device = \"$base$path\"; 409 fsType = \"none\"; 410 options = \[ \"bind\" \]; 411 }; 412 413EOF 414 next; 415 } 416 } 417 $fsByDev{$fields[2]} = $mountPoint; 418 419 # We don't know how to handle FUSE filesystems. 420 if ($fsType eq "fuseblk" || $fsType eq "fuse") { 421 print STDERR "warning: don't know how to emit ‘fileSystem’ option for FUSE filesystem ‘$mountPoint’\n"; 422 next; 423 } 424 425 # Is this a mount of a loopback device? 426 my @extraOptions; 427 if ($device =~ /\/dev\/loop(\d+)/) { 428 my $loopnr = $1; 429 my $backer = read_file "/sys/block/loop$loopnr/loop/backing_file"; 430 if (defined $backer) { 431 chomp $backer; 432 $device = $backer; 433 push @extraOptions, "loop"; 434 } 435 } 436 437 # Is this a btrfs filesystem? 438 if ($fsType eq "btrfs") { 439 my ($status, @info) = runCommand("btrfs subvol show $rootDir$mountPoint"); 440 if ($status != 0 || join("", @info) =~ /ERROR:/) { 441 die "Failed to retrieve subvolume info for $mountPoint\n"; 442 } 443 my @ids = join("\n", @info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s; 444 if ($#ids > 0) { 445 die "Btrfs subvol name for $mountPoint listed multiple times in mount\n" 446 } elsif ($#ids == 0) { 447 my @paths = join("", @info) =~ m/^([^\n]*)/; 448 if ($#paths > 0) { 449 die "Btrfs returned multiple paths for a single subvolume id, mountpoint $mountPoint\n"; 450 } elsif ($#paths != 0) { 451 die "Btrfs did not return a path for the subvolume at $mountPoint\n"; 452 } 453 push @extraOptions, "subvol=$paths[0]"; 454 } 455 } 456 457 # Don't emit tmpfs entry for /tmp, because it most likely comes from the 458 # boot.tmpOnTmpfs option in configuration.nix (managed declaratively). 459 next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs"); 460 461 # Emit the filesystem. 462 $fileSystems .= <<EOF; 463 fileSystems.\"$mountPoint\" = 464 { device = \"${\(findStableDevPath $device)}\"; 465 fsType = \"$fsType\"; 466EOF 467 468 if (scalar @extraOptions > 0) { 469 $fileSystems .= <<EOF; 470 options = \[ ${\join " ", map { "\"" . $_ . "\"" } uniq(@extraOptions)} \]; 471EOF 472 } 473 474 $fileSystems .= <<EOF; 475 }; 476 477EOF 478 479 # If this filesystem is on a LUKS device, then add a 480 # boot.initrd.luks.devices entry. 481 if (-e $device) { 482 my $deviceName = basename(abs_path($device)); 483 if (-e "/sys/class/block/$deviceName" 484 && read_file("/sys/class/block/$deviceName/dm/uuid", err_mode => 'quiet') =~ /^CRYPT-LUKS/) 485 { 486 my @slaves = glob("/sys/class/block/$deviceName/slaves/*"); 487 if (scalar @slaves == 1) { 488 my $slave = "/dev/" . basename($slaves[0]); 489 if (-e $slave) { 490 my $dmName = read_file("/sys/class/block/$deviceName/dm/name"); 491 chomp $dmName; 492 # Ensure to add an entry only once 493 my $luksDevice = " boot.initrd.luks.devices.\"$dmName\".device"; 494 if ($fileSystems !~ /^\Q$luksDevice\E/m) { 495 $fileSystems .= "$luksDevice = \"${\(findStableDevPath $slave)}\";\n\n"; 496 } 497 } 498 } 499 } 500 } 501} 502 503# For lack of a better way to determine it, guess whether we should use a 504# bigger font for the console from the display mode on the first 505# framebuffer. A way based on the physical size/actual DPI reported by 506# the monitor would be nice, but I don't know how to do this without X :) 507my $fb_modes_file = "/sys/class/graphics/fb0/modes"; 508if (-f $fb_modes_file && -r $fb_modes_file) { 509 my $modes = read_file($fb_modes_file); 510 $modes =~ m/([0-9]+)x([0-9]+)/; 511 my $console_width = $1, my $console_height = $2; 512 if ($console_width > 1920) { 513 push @attrs, "# high-resolution display"; 514 push @attrs, 'hardware.video.hidpi.enable = lib.mkDefault true;'; 515 } 516} 517 518 519# Generate the hardware configuration file. 520 521sub toNixStringList { 522 my $res = ""; 523 foreach my $s (@_) { 524 $res .= " \"$s\""; 525 } 526 return $res; 527} 528sub toNixList { 529 my $res = ""; 530 foreach my $s (@_) { 531 $res .= " $s"; 532 } 533 return $res; 534} 535 536sub multiLineList { 537 my $indent = shift; 538 return " [ ]" if !@_; 539 my $res = "\n${indent}[ "; 540 my $first = 1; 541 foreach my $s (@_) { 542 $res .= "$indent " if !$first; 543 $first = 0; 544 $res .= "$s\n"; 545 } 546 $res .= "$indent]"; 547 return $res; 548} 549 550my $initrdAvailableKernelModules = toNixStringList(uniq @initrdAvailableKernelModules); 551my $initrdKernelModules = toNixStringList(uniq @initrdKernelModules); 552my $kernelModules = toNixStringList(uniq @kernelModules); 553my $modulePackages = toNixList(uniq @modulePackages); 554 555my $fsAndSwap = ""; 556if (!$noFilesystems) { 557 $fsAndSwap = "\n$fileSystems "; 558 $fsAndSwap .= "swapDevices =" . multiLineList(" ", @swapDevices) . ";\n"; 559} 560 561my $hwConfig = <<EOF; 562# Do not modify this file! It was generated by ‘nixos-generate-config’ 563# and may be overwritten by future invocations. Please make changes 564# to /etc/nixos/configuration.nix instead. 565{ config, lib, pkgs, modulesPath, ... }: 566 567{ 568 imports =${\multiLineList(" ", @imports)}; 569 570 boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ]; 571 boot.initrd.kernelModules = [$initrdKernelModules ]; 572 boot.kernelModules = [$kernelModules ]; 573 boot.extraModulePackages = [$modulePackages ]; 574$fsAndSwap 575${\join "", (map { " $_\n" } (uniq @attrs))}} 576EOF 577 578sub generateNetworkingDhcpConfig { 579 my $config = <<EOF; 580 # The global useDHCP flag is deprecated, therefore explicitly set to false here. 581 # Per-interface useDHCP will be mandatory in the future, so this generated config 582 # replicates the default behaviour. 583 networking.useDHCP = false; 584EOF 585 586 foreach my $path (glob "/sys/class/net/*") { 587 my $dev = basename($path); 588 if ($dev ne "lo") { 589 $config .= " networking.interfaces.$dev.useDHCP = true;\n"; 590 } 591 } 592 593 return $config; 594} 595 596sub generateXserverConfig { 597 my $xserverEnabled = "@xserverEnabled@"; 598 599 my $config = ""; 600 if ($xserverEnabled eq "1") { 601 $config = <<EOF; 602 # Enable the X11 windowing system. 603 services.xserver.enable = true; 604EOF 605 } else { 606 $config = <<EOF; 607 # Enable the X11 windowing system. 608 # services.xserver.enable = true; 609EOF 610 } 611} 612 613if ($showHardwareConfig) { 614 print STDOUT $hwConfig; 615} else { 616 $outDir = "$rootDir$outDir"; 617 618 my $fn = "$outDir/hardware-configuration.nix"; 619 print STDERR "writing $fn...\n"; 620 mkpath($outDir, 0, 0755); 621 write_file($fn, $hwConfig); 622 623 # Generate a basic configuration.nix, unless one already exists. 624 $fn = "$outDir/configuration.nix"; 625 if ($force || ! -e $fn) { 626 print STDERR "writing $fn...\n"; 627 628 my $bootLoaderConfig = ""; 629 if (-e "/sys/firmware/efi/efivars") { 630 $bootLoaderConfig = <<EOF; 631 # Use the systemd-boot EFI boot loader. 632 boot.loader.systemd-boot.enable = true; 633 boot.loader.efi.canTouchEfiVariables = true; 634EOF 635 } elsif (-e "/boot/extlinux") { 636 $bootLoaderConfig = <<EOF; 637 # Use the extlinux boot loader. (NixOS wants to enable GRUB by default) 638 boot.loader.grub.enable = false; 639 # Enables the generation of /boot/extlinux/extlinux.conf 640 boot.loader.generic-extlinux-compatible.enable = true; 641EOF 642 } elsif ($virt ne "systemd-nspawn") { 643 $bootLoaderConfig = <<EOF; 644 # Use the GRUB 2 boot loader. 645 boot.loader.grub.enable = true; 646 boot.loader.grub.version = 2; 647 # boot.loader.grub.efiSupport = true; 648 # boot.loader.grub.efiInstallAsRemovable = true; 649 # boot.loader.efi.efiSysMountPoint = "/boot/efi"; 650 # Define on which hard drive you want to install Grub. 651 # boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only 652EOF 653 } 654 655 my $networkingDhcpConfig = generateNetworkingDhcpConfig(); 656 657 my $xserverConfig = generateXserverConfig(); 658 659 (my $desktopConfiguration = <<EOF)=~s/^/ /gm; 660@desktopConfiguration@ 661EOF 662 663 write_file($fn, <<EOF); 664@configuration@ 665EOF 666 print STDERR "For more hardware-specific settings, see https://github.com/NixOS/nixos-hardware.\n" 667 } else { 668 print STDERR "warning: not overwriting existing $fn\n"; 669 } 670} 671 672# workaround for a bug in substituteAll