diff options
Diffstat (limited to 'nixos/modules/installer/tools')
-rw-r--r-- | nixos/modules/installer/tools/get-version-suffix | 22 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nix-fallback-paths.nix | 7 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nixos-build-vms/build-vms.nix | 31 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh | 53 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nixos-enter.sh | 109 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nixos-generate-config.pl | 675 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nixos-install.sh | 218 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nixos-option/default.nix | 1 | ||||
-rw-r--r-- | nixos/modules/installer/tools/nixos-version.sh | 24 | ||||
-rw-r--r-- | nixos/modules/installer/tools/tools.nix | 235 |
10 files changed, 1375 insertions, 0 deletions
diff --git a/nixos/modules/installer/tools/get-version-suffix b/nixos/modules/installer/tools/get-version-suffix new file mode 100644 index 00000000000..b8972cd57d2 --- /dev/null +++ b/nixos/modules/installer/tools/get-version-suffix @@ -0,0 +1,22 @@ +getVersion() { + local dir="$1" + rev= + if [ -e "$dir/.git" ]; then + if [ -z "$(type -P git)" ]; then + echo "warning: Git not found; cannot figure out revision of $dir" >&2 + return + fi + cd "$dir" + rev=$(git rev-parse --short HEAD) + if git describe --always --dirty | grep -q dirty; then + rev+=M + fi + fi +} + +if nixpkgs=$(nix-instantiate --find-file nixpkgs "$@"); then + getVersion $nixpkgs + if [ -n "$rev" ]; then + echo ".git.$rev" + fi +fi diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix new file mode 100644 index 00000000000..dfafda77cb5 --- /dev/null +++ b/nixos/modules/installer/tools/nix-fallback-paths.nix @@ -0,0 +1,7 @@ +{ + x86_64-linux = "/nix/store/0n2wfvi1i3fg97cjc54wslvk0804y0sn-nix-2.7.0"; + i686-linux = "/nix/store/4p27c1k9z99pli6x8cxfph20yfyzn9nh-nix-2.7.0"; + aarch64-linux = "/nix/store/r9yr8ijsb0gi9r7y92y3yzyld59yp0kj-nix-2.7.0"; + x86_64-darwin = "/nix/store/hyfj5imsd0c4amlcjpf8l6w4q2draaj3-nix-2.7.0"; + aarch64-darwin = "/nix/store/9l96qllhbb6xrsjaai76dn74ap7rq92n-nix-2.7.0"; +} diff --git a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix new file mode 100644 index 00000000000..b4a94f62ad9 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix @@ -0,0 +1,31 @@ +{ system ? builtins.currentSystem +, config ? {} +, networkExpr +}: + +let + nodes = builtins.mapAttrs (vm: module: { + _file = "${networkExpr}@node-${vm}"; + imports = [ module ]; + }) (import networkExpr); + + pkgs = import ../../../../.. { inherit system config; }; + + testing = import ../../../../lib/testing-python.nix { + inherit system pkgs; + }; + + interactiveDriver = (testing.makeTest { inherit nodes; testScript = "start_all(); join_all();"; }).driverInteractive; +in + + +pkgs.runCommand "nixos-build-vms" { nativeBuildInputs = [ pkgs.makeWrapper ]; } '' + mkdir -p $out/bin + ln -s ${interactiveDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver + ln -s ${interactiveDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms + wrapProgram $out/bin/nixos-test-driver \ + --add-flags "--interactive" + wrapProgram $out/bin/nixos-run-vms \ + --set testScript "${pkgs.writeText "start-all" "start_all(); join_all();"}" \ + --add-flags "--no-interactive" +'' diff --git a/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh b/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh new file mode 100644 index 00000000000..490ede04e6b --- /dev/null +++ b/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh @@ -0,0 +1,53 @@ +#! @runtimeShell@ -e +# shellcheck shell=bash + +# Shows the usage of this command to the user + +showUsage() { + exec man nixos-build-vms + exit 1 +} + +# Parse valid argument options + +nixBuildArgs=() +networkExpr= + +while [ $# -gt 0 ]; do + case "$1" in + --no-out-link) + nixBuildArgs+=("--no-out-link") + ;; + --show-trace) + nixBuildArgs+=("--show-trace") + ;; + -h|--help) + showUsage + exit 0 + ;; + --option) + shift + nixBuildArgs+=("--option" "$1" "$2"); shift + ;; + *) + if [ -n "$networkExpr" ]; then + echo "Network expression already set!" + showUsage + exit 1 + fi + networkExpr="$(readlink -f "$1")" + ;; + esac + + shift +done + +if [ -z "$networkExpr" ] +then + echo "ERROR: A network expression must be specified!" >&2 + exit 1 +fi + +# Build a network of VMs +nix-build '<nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix>' \ + --argstr networkExpr "$networkExpr" "${nixBuildArgs[@]}" diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh new file mode 100644 index 00000000000..89beeee7cf9 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-enter.sh @@ -0,0 +1,109 @@ +#! @runtimeShell@ +# shellcheck shell=bash + +set -e + +# Re-exec ourselves in a private mount namespace so that our bind +# mounts get cleaned up automatically. +if [ -z "$NIXOS_ENTER_REEXEC" ]; then + export NIXOS_ENTER_REEXEC=1 + if [ "$(id -u)" != 0 ]; then + extraFlags="-r" + fi + exec unshare --fork --mount --uts --mount-proc --pid $extraFlags -- "$0" "$@" +else + mount --make-rprivate / +fi + +mountPoint=/mnt +system=/nix/var/nix/profiles/system +command=("$system/sw/bin/bash" "--login") +silent=0 + +while [ "$#" -gt 0 ]; do + i="$1"; shift 1 + case "$i" in + --root) + mountPoint="$1"; shift 1 + ;; + --system) + system="$1"; shift 1 + ;; + --help) + exec man nixos-enter + exit 1 + ;; + --command|-c) + command=("$system/sw/bin/bash" "-c" "$1") + shift 1 + ;; + --silent) + silent=1 + ;; + --) + command=("$@") + break + ;; + *) + echo "$0: unknown option \`$i'" + exit 1 + ;; + esac +done + +if [[ ! -e $mountPoint/etc/NIXOS ]]; then + echo "$0: '$mountPoint' is not a NixOS installation" >&2 + exit 126 +fi + +mkdir -p "$mountPoint/dev" "$mountPoint/sys" +chmod 0755 "$mountPoint/dev" "$mountPoint/sys" +mount --rbind /dev "$mountPoint/dev" +mount --rbind /sys "$mountPoint/sys" + +# modified from https://github.com/archlinux/arch-install-scripts/blob/bb04ab435a5a89cd5e5ee821783477bc80db797f/arch-chroot.in#L26-L52 +chroot_add_resolv_conf() { + local chrootDir="$1" resolvConf="$1/etc/resolv.conf" + + [[ -e /etc/resolv.conf ]] || return 0 + + # Handle resolv.conf as a symlink to somewhere else. + if [[ -L "$resolvConf" ]]; then + # readlink(1) should always give us *something* since we know at this point + # it's a symlink. For simplicity, ignore the case of nested symlinks. + # We also ignore the possibility of `../`s escaping the root. + resolvConf="$(readlink "$resolvConf")" + if [[ "$resolvConf" = /* ]]; then + resolvConf="$chrootDir$resolvConf" + else + resolvConf="$chrootDir/etc/$resolvConf" + fi + fi + + # ensure file exists to bind mount over + if [[ ! -f "$resolvConf" ]]; then + install -Dm644 /dev/null "$resolvConf" || return 1 + fi + + mount --bind /etc/resolv.conf "$resolvConf" +} + +chroot_add_resolv_conf "$mountPoint" || echo "$0: failed to set up resolv.conf" >&2 + +( + # If silent, write both stdout and stderr of activation script to /dev/null + # otherwise, write both streams to stderr of this process + if [ "$silent" -eq 1 ]; then + exec 2>/dev/null + fi + + # Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings. + LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" IN_NIXOS_ENTER=1 chroot "$mountPoint" "$system/activate" 1>&2 || true + + # Create /tmp + chroot "$mountPoint" systemd-tmpfiles --create --remove --exclude-prefix=/dev 1>&2 || true +) + +unset TMPDIR + +exec chroot "$mountPoint" "${command[@]}" diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl new file mode 100644 index 00000000000..57aef50a0f6 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-generate-config.pl @@ -0,0 +1,675 @@ +#! @perl@ + +use strict; +use Cwd 'abs_path'; +use File::Spec; +use File::Path; +use File::Basename; +use File::Slurp; +use File::stat; + +umask(0022); + +sub uniq { + my %seen; + my @res = (); + foreach my $s (@_) { + if (!defined $seen{$s}) { + $seen{$s} = 1; + push @res, $s; + } + } + return @res; +} + +sub runCommand { + my ($cmd) = @_; + open FILE, "$cmd 2>&1 |" or die "Failed to execute: $cmd\n"; + my @ret = <FILE>; + close FILE; + return ($?, @ret); +} + +# Process the command line. +my $outDir = "/etc/nixos"; +my $rootDir = ""; # = / +my $force = 0; +my $noFilesystems = 0; +my $showHardwareConfig = 0; + +for (my $n = 0; $n < scalar @ARGV; $n++) { + my $arg = $ARGV[$n]; + if ($arg eq "--help") { + exec "man nixos-generate-config" or die; + } + elsif ($arg eq "--dir") { + $n++; + $outDir = $ARGV[$n]; + die "$0: ‘--dir’ requires an argument\n" unless defined $outDir; + } + elsif ($arg eq "--root") { + $n++; + $rootDir = $ARGV[$n]; + die "$0: ‘--root’ requires an argument\n" unless defined $rootDir; + $rootDir =~ s/\/*$//; # remove trailing slashes + } + elsif ($arg eq "--force") { + $force = 1; + } + elsif ($arg eq "--no-filesystems") { + $noFilesystems = 1; + } + elsif ($arg eq "--show-hardware-config") { + $showHardwareConfig = 1; + } + else { + die "$0: unrecognized argument ‘$arg’\n"; + } +} + + +my @attrs = (); +my @kernelModules = (); +my @initrdKernelModules = (); +my @initrdAvailableKernelModules = (); +my @modulePackages = (); +my @imports; + + +sub debug { + return unless defined $ENV{"DEBUG"}; + print STDERR @_; +} + + +my $cpuinfo = read_file "/proc/cpuinfo"; + + +sub hasCPUFeature { + my $feature = shift; + return $cpuinfo =~ /^flags\s*:.* $feature( |$)/m; +} + + +sub cpuManufacturer { + my $id = shift; + return $cpuinfo =~ /^vendor_id\s*:.* $id$/m; +} + + +# Determine CPU governor to use +if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") { + my $governors = read_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"); + # ondemand governor is not available on sandy bridge or later Intel CPUs + my @desired_governors = ("ondemand", "powersave"); + my $e; + + foreach $e (@desired_governors) { + if (index($governors, $e) != -1) { + last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";"); + } + } +} + + +# Virtualization support? +push @kernelModules, "kvm-intel" if hasCPUFeature "vmx"; +push @kernelModules, "kvm-amd" if hasCPUFeature "svm"; + +push @attrs, "hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "AuthenticAMD"; +push @attrs, "hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "GenuineIntel"; + + +# Look at the PCI devices and add necessary modules. Note that most +# modules are auto-detected so we don't need to list them here. +# However, some are needed in the initrd to boot the system. + +my $videoDriver; + +sub pciCheck { + my $path = shift; + my $vendor = read_file "$path/vendor"; chomp $vendor; + my $device = read_file "$path/device"; chomp $device; + my $class = read_file "$path/class"; chomp $class; + + my $module; + if (-e "$path/driver/module") { + $module = basename `readlink -f $path/driver/module`; + chomp $module; + } + + debug "$path: $vendor $device $class"; + debug " $module" if defined $module; + debug "\n"; + + if (defined $module) { + # See the bottom of http://pciids.sourceforge.net/pci.ids for + # device classes. + if (# Mass-storage controller. Definitely important. + $class =~ /^0x01/ || + + # Firewire controller. A disk might be attached. + $class =~ /^0x0c00/ || + + # USB controller. Needed if we want to use the + # keyboard when things go wrong in the initrd. + $class =~ /^0x0c03/ + ) + { + push @initrdAvailableKernelModules, $module; + } + } + + # broadcom STA driver (wl.ko) + # list taken from http://www.broadcom.com/docs/linux_sta/README.txt + if ($vendor eq "0x14e4" && + ($device eq "0x4311" || $device eq "0x4312" || $device eq "0x4313" || + $device eq "0x4315" || $device eq "0x4327" || $device eq "0x4328" || + $device eq "0x4329" || $device eq "0x432a" || $device eq "0x432b" || + $device eq "0x432c" || $device eq "0x432d" || $device eq "0x4353" || + $device eq "0x4357" || $device eq "0x4358" || $device eq "0x4359" || + $device eq "0x4331" || $device eq "0x43a0" || $device eq "0x43b1" + ) ) + { + push @modulePackages, "config.boot.kernelPackages.broadcom_sta"; + push @kernelModules, "wl"; + } + + # broadcom FullMac driver + # list taken from + # https://wireless.wiki.kernel.org/en/users/Drivers/brcm80211#brcmfmac + if ($vendor eq "0x14e4" && + ($device eq "0x43a3" || $device eq "0x43df" || $device eq "0x43ec" || + $device eq "0x43d3" || $device eq "0x43d9" || $device eq "0x43e9" || + $device eq "0x43ba" || $device eq "0x43bb" || $device eq "0x43bc" || + $device eq "0xaa52" || $device eq "0x43ca" || $device eq "0x43cb" || + $device eq "0x43cc" || $device eq "0x43c3" || $device eq "0x43c4" || + $device eq "0x43c5" + ) ) + { + # we need e.g. brcmfmac43602-pcie.bin + push @imports, "(modulesPath + \"/hardware/network/broadcom-43xx.nix\")"; + } + + # In case this is a virtio scsi device, we need to explicitly make this available. + if ($vendor eq "0x1af4" && $device eq "0x1004") { + push @initrdAvailableKernelModules, "virtio_scsi"; + } + + # Can't rely on $module here, since the module may not be loaded + # due to missing firmware. Ideally we would check modules.pcimap + # here. + push @attrs, "networking.enableIntel2200BGFirmware = true;" if + $vendor eq "0x8086" && + ($device eq "0x1043" || $device eq "0x104f" || $device eq "0x4220" || + $device eq "0x4221" || $device eq "0x4223" || $device eq "0x4224"); + + push @attrs, "networking.enableIntel3945ABGFirmware = true;" if + $vendor eq "0x8086" && + ($device eq "0x4229" || $device eq "0x4230" || + $device eq "0x4222" || $device eq "0x4227"); + + # Assume that all NVIDIA cards are supported by the NVIDIA driver. + # There may be exceptions (e.g. old cards). + # FIXME: do we want to enable an unfree driver here? + #$videoDriver = "nvidia" if $vendor eq "0x10de" && $class =~ /^0x03/; +} + +foreach my $path (glob "/sys/bus/pci/devices/*") { + pciCheck $path; +} + +# Idem for USB devices. + +sub usbCheck { + my $path = shift; + my $class = read_file "$path/bInterfaceClass"; chomp $class; + my $subclass = read_file "$path/bInterfaceSubClass"; chomp $subclass; + my $protocol = read_file "$path/bInterfaceProtocol"; chomp $protocol; + + my $module; + if (-e "$path/driver/module") { + $module = basename `readlink -f $path/driver/module`; + chomp $module; + } + + debug "$path: $class $subclass $protocol"; + debug " $module" if defined $module; + debug "\n"; + + if (defined $module) { + if (# Mass-storage controller. Definitely important. + $class eq "08" || + + # Keyboard. Needed if we want to use the + # keyboard when things go wrong in the initrd. + ($class eq "03" && $protocol eq "01") + ) + { + push @initrdAvailableKernelModules, $module; + } + } +} + +foreach my $path (glob "/sys/bus/usb/devices/*") { + if (-e "$path/bInterfaceClass") { + usbCheck $path; + } +} + + +# Add the modules for all block and MMC devices. +foreach my $path (glob "/sys/class/{block,mmc_host}/*") { + my $module; + if (-e "$path/device/driver/module") { + $module = basename `readlink -f $path/device/driver/module`; + chomp $module; + push @initrdAvailableKernelModules, $module; + } +} + +# Add bcache module, if needed. +my @bcacheDevices = glob("/dev/bcache*"); +if (scalar @bcacheDevices > 0) { + push @initrdAvailableKernelModules, "bcache"; +} + +# Prevent unbootable systems if LVM snapshots are present at boot time. +if (`lsblk -o TYPE` =~ "lvm") { + push @initrdKernelModules, "dm-snapshot"; +} + +my $virt = `@detectvirt@`; +chomp $virt; + + +# Check if we're a VirtualBox guest. If so, enable the guest +# additions. +if ($virt eq "oracle") { + push @attrs, "virtualisation.virtualbox.guest.enable = true;" +} + + +# Likewise for QEMU. +if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") { + push @imports, "(modulesPath + \"/profiles/qemu-guest.nix\")"; +} + +# Also for Hyper-V. +if ($virt eq "microsoft") { + push @attrs, "virtualisation.hypervGuest.enable = true;" +} + + +# Pull in NixOS configuration for containers. +if ($virt eq "systemd-nspawn") { + push @attrs, "boot.isContainer = true;"; +} + + +# Provide firmware for devices that are not detected by this script, +# unless we're in a VM/container. +push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")" + if $virt eq "none"; + + +# For a device name like /dev/sda1, find a more stable path like +# /dev/disk/by-uuid/X or /dev/disk/by-label/Y. +sub findStableDevPath { + my ($dev) = @_; + return $dev if substr($dev, 0, 1) ne "/"; + return $dev unless -e $dev; + + my $st = stat($dev) or return $dev; + + foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) { + my $st2 = stat($dev2) or next; + return $dev2 if $st->rdev == $st2->rdev; + } + + return $dev; +} + +push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDriver; + +# Generate the swapDevices option from the currently activated swap +# devices. +my @swaps = read_file("/proc/swaps", err_mode => 'carp'); +my @swapDevices; +if (@swaps) { + shift @swaps; + foreach my $swap (@swaps) { + my @fields = split ' ', $swap; + my $swapFilename = $fields[0]; + my $swapType = $fields[1]; + next unless -e $swapFilename; + my $dev = findStableDevPath $swapFilename; + if ($swapType =~ "partition") { + # zram devices are more likely created by configuration.nix, so + # ignore them here + next if ($swapFilename =~ /^\/dev\/zram/); + push @swapDevices, "{ device = \"$dev\"; }"; + } elsif ($swapType =~ "file") { + # swap *files* are more likely specified in configuration.nix, so + # ignore them here. + } else { + die "Unsupported swap type: $swapType\n"; + } + } +} + + +# Generate the fileSystems option from the currently mounted +# filesystems. +sub in { + my ($d1, $d2) = @_; + return $d1 eq $d2 || substr($d1, 0, length($d2) + 1) eq "$d2/"; +} + +my $fileSystems; +my %fsByDev; +foreach my $fs (read_file("/proc/self/mountinfo")) { + chomp $fs; + my @fields = split / /, $fs; + my $mountPoint = $fields[4]; + $mountPoint =~ s/\\040/ /g; # account for mount points with spaces in the name (\040 is the escape character) + $mountPoint =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character) + next unless -d $mountPoint; + my @mountOptions = split /,/, $fields[5]; + + next if !in($mountPoint, $rootDir); + $mountPoint = substr($mountPoint, length($rootDir)); # strip the root directory (e.g. /mnt) + $mountPoint = "/" if $mountPoint eq ""; + + # Skip special filesystems. + next if in($mountPoint, "/proc") || in($mountPoint, "/dev") || in($mountPoint, "/sys") || in($mountPoint, "/run") || $mountPoint eq "/var/lib/nfs/rpc_pipefs"; + + # Skip the optional fields. + my $n = 6; $n++ while $fields[$n] ne "-"; $n++; + my $fsType = $fields[$n]; + my $device = $fields[$n + 1]; + my @superOptions = split /,/, $fields[$n + 2]; + $device =~ s/\\040/ /g; # account for devices with spaces in the name (\040 is the escape character) + $device =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character) + + # Skip the read-only bind-mount on /nix/store. + next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions); + + # Maybe this is a bind-mount of a filesystem we saw earlier? + if (defined $fsByDev{$fields[2]}) { + # Make sure this isn't a btrfs subvolume. + my $msg = `@btrfs@ subvol show $rootDir$mountPoint`; + if ($? != 0 || $msg =~ /ERROR:/s) { + my $path = $fields[3]; $path = "" if $path eq "/"; + my $base = $fsByDev{$fields[2]}; + $base = "" if $base eq "/"; + $fileSystems .= <<EOF; + fileSystems.\"$mountPoint\" = + { device = \"$base$path\"; + fsType = \"none\"; + options = \[ \"bind\" \]; + }; + +EOF + next; + } + } + $fsByDev{$fields[2]} = $mountPoint; + + # We don't know how to handle FUSE filesystems. + if ($fsType eq "fuseblk" || $fsType eq "fuse") { + print STDERR "warning: don't know how to emit ‘fileSystem’ option for FUSE filesystem ‘$mountPoint’\n"; + next; + } + + # Is this a mount of a loopback device? + my @extraOptions; + if ($device =~ /\/dev\/loop(\d+)/) { + my $loopnr = $1; + my $backer = read_file "/sys/block/loop$loopnr/loop/backing_file"; + if (defined $backer) { + chomp $backer; + $device = $backer; + push @extraOptions, "loop"; + } + } + + # Is this a btrfs filesystem? + if ($fsType eq "btrfs") { + my ($status, @info) = runCommand("@btrfs@ subvol show $rootDir$mountPoint"); + if ($status != 0 || join("", @info) =~ /ERROR:/) { + die "Failed to retrieve subvolume info for $mountPoint\n"; + } + my @ids = join("\n", @info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s; + if ($#ids > 0) { + die "Btrfs subvol name for $mountPoint listed multiple times in mount\n" + } elsif ($#ids == 0) { + my @paths = join("", @info) =~ m/^([^\n]*)/; + if ($#paths > 0) { + die "Btrfs returned multiple paths for a single subvolume id, mountpoint $mountPoint\n"; + } elsif ($#paths != 0) { + die "Btrfs did not return a path for the subvolume at $mountPoint\n"; + } + push @extraOptions, "subvol=$paths[0]"; + } + } + + # Don't emit tmpfs entry for /tmp, because it most likely comes from the + # boot.tmpOnTmpfs option in configuration.nix (managed declaratively). + next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs"); + + # Emit the filesystem. + $fileSystems .= <<EOF; + fileSystems.\"$mountPoint\" = + { device = \"${\(findStableDevPath $device)}\"; + fsType = \"$fsType\"; +EOF + + if (scalar @extraOptions > 0) { + $fileSystems .= <<EOF; + options = \[ ${\join " ", map { "\"" . $_ . "\"" } uniq(@extraOptions)} \]; +EOF + } + + $fileSystems .= <<EOF; + }; + +EOF + + # If this filesystem is on a LUKS device, then add a + # boot.initrd.luks.devices entry. + if (-e $device) { + my $deviceName = basename(abs_path($device)); + if (-e "/sys/class/block/$deviceName" + && read_file("/sys/class/block/$deviceName/dm/uuid", err_mode => 'quiet') =~ /^CRYPT-LUKS/) + { + my @slaves = glob("/sys/class/block/$deviceName/slaves/*"); + if (scalar @slaves == 1) { + my $slave = "/dev/" . basename($slaves[0]); + if (-e $slave) { + my $dmName = read_file("/sys/class/block/$deviceName/dm/name"); + chomp $dmName; + # Ensure to add an entry only once + my $luksDevice = " boot.initrd.luks.devices.\"$dmName\".device"; + if ($fileSystems !~ /^\Q$luksDevice\E/m) { + $fileSystems .= "$luksDevice = \"${\(findStableDevPath $slave)}\";\n\n"; + } + } + } + } + } +} + +# For lack of a better way to determine it, guess whether we should use a +# bigger font for the console from the display mode on the first +# framebuffer. A way based on the physical size/actual DPI reported by +# the monitor would be nice, but I don't know how to do this without X :) +my $fb_modes_file = "/sys/class/graphics/fb0/modes"; +if (-f $fb_modes_file && -r $fb_modes_file) { + my $modes = read_file($fb_modes_file); + $modes =~ m/([0-9]+)x([0-9]+)/; + my $console_width = $1, my $console_height = $2; + if ($console_width > 1920) { + push @attrs, "# high-resolution display"; + push @attrs, 'hardware.video.hidpi.enable = lib.mkDefault true;'; + } +} + + +# Generate the hardware configuration file. + +sub toNixStringList { + my $res = ""; + foreach my $s (@_) { + $res .= " \"$s\""; + } + return $res; +} +sub toNixList { + my $res = ""; + foreach my $s (@_) { + $res .= " $s"; + } + return $res; +} + +sub multiLineList { + my $indent = shift; + return " [ ]" if !@_; + my $res = "\n${indent}[ "; + my $first = 1; + foreach my $s (@_) { + $res .= "$indent " if !$first; + $first = 0; + $res .= "$s\n"; + } + $res .= "$indent]"; + return $res; +} + +my $initrdAvailableKernelModules = toNixStringList(uniq @initrdAvailableKernelModules); +my $initrdKernelModules = toNixStringList(uniq @initrdKernelModules); +my $kernelModules = toNixStringList(uniq @kernelModules); +my $modulePackages = toNixList(uniq @modulePackages); + +my $fsAndSwap = ""; +if (!$noFilesystems) { + $fsAndSwap = "\n$fileSystems "; + $fsAndSwap .= "swapDevices =" . multiLineList(" ", @swapDevices) . ";\n"; +} + +my $networkingDhcpConfig = generateNetworkingDhcpConfig(); + +my $hwConfig = <<EOF; +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports =${\multiLineList(" ", @imports)}; + + boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ]; + boot.initrd.kernelModules = [$initrdKernelModules ]; + boot.kernelModules = [$kernelModules ]; + boot.extraModulePackages = [$modulePackages ]; +$fsAndSwap +$networkingDhcpConfig +${\join "", (map { " $_\n" } (uniq @attrs))}} +EOF + +sub generateNetworkingDhcpConfig { + my $config = <<EOF; + # The global useDHCP flag is deprecated, therefore explicitly set to false here. + # Per-interface useDHCP will be mandatory in the future, so this generated config + # replicates the default behaviour. + networking.useDHCP = lib.mkDefault false; +EOF + + foreach my $path (glob "/sys/class/net/*") { + my $dev = basename($path); + if ($dev ne "lo") { + $config .= " networking.interfaces.$dev.useDHCP = lib.mkDefault true;\n"; + } + } + + return $config; +} + +sub generateXserverConfig { + my $xserverEnabled = "@xserverEnabled@"; + + my $config = ""; + if ($xserverEnabled eq "1") { + $config = <<EOF; + # Enable the X11 windowing system. + services.xserver.enable = true; +EOF + } else { + $config = <<EOF; + # Enable the X11 windowing system. + # services.xserver.enable = true; +EOF + } +} + +if ($showHardwareConfig) { + print STDOUT $hwConfig; +} else { + $outDir = "$rootDir$outDir"; + + my $fn = "$outDir/hardware-configuration.nix"; + print STDERR "writing $fn...\n"; + mkpath($outDir, 0, 0755); + write_file($fn, $hwConfig); + + # Generate a basic configuration.nix, unless one already exists. + $fn = "$outDir/configuration.nix"; + if ($force || ! -e $fn) { + print STDERR "writing $fn...\n"; + + my $bootLoaderConfig = ""; + if (-e "/sys/firmware/efi/efivars") { + $bootLoaderConfig = <<EOF; + # Use the systemd-boot EFI boot loader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; +EOF + } elsif (-e "/boot/extlinux") { + $bootLoaderConfig = <<EOF; + # Use the extlinux boot loader. (NixOS wants to enable GRUB by default) + boot.loader.grub.enable = false; + # Enables the generation of /boot/extlinux/extlinux.conf + boot.loader.generic-extlinux-compatible.enable = true; +EOF + } elsif ($virt ne "systemd-nspawn") { + $bootLoaderConfig = <<EOF; + # Use the GRUB 2 boot loader. + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + # boot.loader.grub.efiSupport = true; + # boot.loader.grub.efiInstallAsRemovable = true; + # boot.loader.efi.efiSysMountPoint = "/boot/efi"; + # Define on which hard drive you want to install Grub. + # boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only +EOF + } + + my $networkingDhcpConfig = generateNetworkingDhcpConfig(); + + my $xserverConfig = generateXserverConfig(); + + (my $desktopConfiguration = <<EOF)=~s/^/ /gm; +@desktopConfiguration@ +EOF + + write_file($fn, <<EOF); +@configuration@ +EOF + print STDERR "For more hardware-specific settings, see https://github.com/NixOS/nixos-hardware.\n" + } else { + print STDERR "warning: not overwriting existing $fn\n"; + } +} + +# workaround for a bug in substituteAll diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh new file mode 100644 index 00000000000..e7cf52f5e32 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-install.sh @@ -0,0 +1,218 @@ +#! @runtimeShell@ +# shellcheck shell=bash + +set -e +shopt -s nullglob + +export PATH=@path@:$PATH + +# Ensure a consistent umask. +umask 0022 + +# Parse the command line for the -I flag +extraBuildFlags=() +flakeFlags=() + +mountPoint=/mnt +channelPath= +system= +verbosity=() + +while [ "$#" -gt 0 ]; do + i="$1"; shift 1 + case "$i" in + --max-jobs|-j|--cores|-I|--substituters) + j="$1"; shift 1 + extraBuildFlags+=("$i" "$j") + ;; + --option) + j="$1"; shift 1 + k="$1"; shift 1 + extraBuildFlags+=("$i" "$j" "$k") + ;; + --root) + mountPoint="$1"; shift 1 + ;; + --system|--closure) + system="$1"; shift 1 + ;; + --flake) + flake="$1" + flakeFlags=(--experimental-features 'nix-command flakes') + shift 1 + ;; + --recreate-lock-file|--no-update-lock-file|--no-write-lock-file|--no-registries|--commit-lock-file) + lockFlags+=("$i") + ;; + --update-input) + j="$1"; shift 1 + lockFlags+=("$i" "$j") + ;; + --override-input) + j="$1"; shift 1 + k="$1"; shift 1 + lockFlags+=("$i" "$j" "$k") + ;; + --channel) + channelPath="$1"; shift 1 + ;; + --no-channel-copy) + noChannelCopy=1 + ;; + --no-root-password|--no-root-passwd) + noRootPasswd=1 + ;; + --no-bootloader) + noBootLoader=1 + ;; + --show-trace|--impure|--keep-going) + extraBuildFlags+=("$i") + ;; + --help) + exec man nixos-install + exit 1 + ;; + --debug) + set -x + ;; + -v*|--verbose) + verbosity+=("$i") + ;; + *) + echo "$0: unknown option \`$i'" + exit 1 + ;; + esac +done + +if ! test -e "$mountPoint"; then + echo "mount point $mountPoint doesn't exist" + exit 1 +fi + +# Verify permissions are okay-enough +checkPath="$(realpath "$mountPoint")" +while [[ "$checkPath" != "/" ]]; do + mode="$(stat -c '%a' "$checkPath")" + if [[ "${mode: -1}" -lt "5" ]]; then + echo "path $checkPath should have permissions 755, but had permissions $mode. Consider running 'chmod o+rx $checkPath'." + exit 1 + fi + checkPath="$(dirname "$checkPath")" +done + +# Get the path of the NixOS configuration file. +if [[ -z $NIXOS_CONFIG ]]; then + NIXOS_CONFIG=$mountPoint/etc/nixos/configuration.nix +fi + +if [[ ${NIXOS_CONFIG:0:1} != / ]]; then + echo "$0: \$NIXOS_CONFIG is not an absolute path" + exit 1 +fi + +if [[ -n $flake ]]; then + if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then + flake="${BASH_REMATCH[1]}" + flakeAttr="${BASH_REMATCH[2]}" + fi + if [[ -z "$flakeAttr" ]]; then + echo "Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri." + echo "For example, to use the output nixosConfigurations.foo from the flake.nix, append \"#foo\" to the flake-uri." + exit 1 + fi + flakeAttr="nixosConfigurations.\"$flakeAttr\"" +fi + +# Resolve the flake. +if [[ -n $flake ]]; then + flake=$(nix "${flakeFlags[@]}" flake metadata --json "${extraBuildFlags[@]}" "${lockFlags[@]}" -- "$flake" | jq -r .url) +fi + +if [[ ! -e $NIXOS_CONFIG && -z $system && -z $flake ]]; then + echo "configuration file $NIXOS_CONFIG doesn't exist" + exit 1 +fi + +# A place to drop temporary stuff. +tmpdir="$(mktemp -d -p "$mountPoint")" +trap 'rm -rf $tmpdir' EXIT + +# store temporary files on target filesystem by default +export TMPDIR=${TMPDIR:-$tmpdir} + +sub="auto?trusted=1" + +# Copy the NixOS/Nixpkgs sources to the target as the initial contents +# of the NixOS channel. +if [[ -z $noChannelCopy ]]; then + if [[ -z $channelPath ]]; then + channelPath="$(nix-env -p /nix/var/nix/profiles/per-user/root/channels -q nixos --no-name --out-path 2>/dev/null || echo -n "")" + fi + if [[ -n $channelPath ]]; then + echo "copying channel..." + mkdir -p "$mountPoint"/nix/var/nix/profiles/per-user/root + nix-env --store "$mountPoint" "${extraBuildFlags[@]}" --extra-substituters "$sub" \ + -p "$mountPoint"/nix/var/nix/profiles/per-user/root/channels --set "$channelPath" --quiet \ + "${verbosity[@]}" + install -m 0700 -d "$mountPoint"/root/.nix-defexpr + ln -sfn /nix/var/nix/profiles/per-user/root/channels "$mountPoint"/root/.nix-defexpr/channels + fi +fi + +# Build the system configuration in the target filesystem. +if [[ -z $system ]]; then + outLink="$tmpdir/system" + if [[ -z $flake ]]; then + echo "building the configuration in $NIXOS_CONFIG..." + nix-build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \ + --extra-substituters "$sub" \ + '<nixpkgs/nixos>' -A system -I "nixos-config=$NIXOS_CONFIG" "${verbosity[@]}" + else + echo "building the flake in $flake..." + nix "${flakeFlags[@]}" build "$flake#$flakeAttr.config.system.build.toplevel" \ + --store "$mountPoint" --extra-substituters "$sub" "${verbosity[@]}" \ + "${extraBuildFlags[@]}" "${lockFlags[@]}" --out-link "$outLink" + fi + system=$(readlink -f "$outLink") +fi + +# Set the system profile to point to the configuration. TODO: combine +# this with the previous step once we have a nix-env replacement with +# a progress bar. +nix-env --store "$mountPoint" "${extraBuildFlags[@]}" \ + --extra-substituters "$sub" \ + -p "$mountPoint"/nix/var/nix/profiles/system --set "$system" "${verbosity[@]}" + +# Mark the target as a NixOS installation, otherwise switch-to-configuration will chicken out. +mkdir -m 0755 -p "$mountPoint/etc" +touch "$mountPoint/etc/NIXOS" + +# Switch to the new system configuration. This will install Grub with +# a menu default pointing at the kernel/initrd/etc of the new +# configuration. +if [[ -z $noBootLoader ]]; then + echo "installing the boot loader..." + # Grub needs an mtab. + ln -sfn /proc/mounts "$mountPoint"/etc/mtab + NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root "$mountPoint" -- /run/current-system/bin/switch-to-configuration boot +fi + +# Ask the user to set a root password, but only if the passwd command +# exists (i.e. when mutable user accounts are enabled). +if [[ -z $noRootPasswd ]] && [ -t 0 ]; then + if nixos-enter --root "$mountPoint" -c 'test -e /nix/var/nix/profiles/system/sw/bin/passwd'; then + set +e + nixos-enter --root "$mountPoint" -c 'echo "setting root password..." && /nix/var/nix/profiles/system/sw/bin/passwd' + exit_code=$? + set -e + + if [[ $exit_code != 0 ]]; then + echo "Setting a root password failed with the above printed error." + echo "You can set the root password manually by executing \`nixos-enter --root ${mountPoint@Q}\` and then running \`passwd\` in the shell of the new system." + exit $exit_code + fi + fi +fi + +echo "installation finished!" diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix new file mode 100644 index 00000000000..061460f38a3 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-option/default.nix @@ -0,0 +1 @@ +{ pkgs, ... }: pkgs.nixos-option diff --git a/nixos/modules/installer/tools/nixos-version.sh b/nixos/modules/installer/tools/nixos-version.sh new file mode 100644 index 00000000000..59a9c572b41 --- /dev/null +++ b/nixos/modules/installer/tools/nixos-version.sh @@ -0,0 +1,24 @@ +#! @runtimeShell@ +# shellcheck shell=bash + +case "$1" in + -h|--help) + exec man nixos-version + exit 1 + ;; + --hash|--revision) + if ! [[ @revision@ =~ ^[0-9a-f]+$ ]]; then + echo "$0: Nixpkgs commit hash is unknown" + exit 1 + fi + echo "@revision@" + ;; + --json) + cat <<EOF +@json@ +EOF + ;; + *) + echo "@version@ (@codeName@)" + ;; +esac diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix new file mode 100644 index 00000000000..71aaf7f253d --- /dev/null +++ b/nixos/modules/installer/tools/tools.nix @@ -0,0 +1,235 @@ +# This module generates nixos-install, nixos-rebuild, +# nixos-generate-config, etc. + +{ config, lib, pkgs, ... }: + +with lib; + +let + makeProg = args: pkgs.substituteAll (args // { + dir = "bin"; + isExecutable = true; + }); + + nixos-build-vms = makeProg { + name = "nixos-build-vms"; + src = ./nixos-build-vms/nixos-build-vms.sh; + inherit (pkgs) runtimeShell; + }; + + nixos-install = makeProg { + name = "nixos-install"; + src = ./nixos-install.sh; + inherit (pkgs) runtimeShell; + nix = config.nix.package.out; + path = makeBinPath [ + pkgs.jq + nixos-enter + ]; + }; + + nixos-rebuild = pkgs.nixos-rebuild.override { nix = config.nix.package.out; }; + + nixos-generate-config = makeProg { + name = "nixos-generate-config"; + src = ./nixos-generate-config.pl; + perl = "${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl"; + detectvirt = "${pkgs.systemd}/bin/systemd-detect-virt"; + btrfs = "${pkgs.btrfs-progs}/bin/btrfs"; + inherit (config.system.nixos-generate-config) configuration desktopConfiguration; + xserverEnabled = config.services.xserver.enable; + }; + + nixos-option = + if lib.versionAtLeast (lib.getVersion config.nix.package) "2.4pre" + then null + else pkgs.nixos-option; + + nixos-version = makeProg { + name = "nixos-version"; + src = ./nixos-version.sh; + inherit (pkgs) runtimeShell; + inherit (config.system.nixos) version codeName revision; + inherit (config.system) configurationRevision; + json = builtins.toJSON ({ + nixosVersion = config.system.nixos.version; + } // optionalAttrs (config.system.nixos.revision != null) { + nixpkgsRevision = config.system.nixos.revision; + } // optionalAttrs (config.system.configurationRevision != null) { + configurationRevision = config.system.configurationRevision; + }); + }; + + nixos-enter = makeProg { + name = "nixos-enter"; + src = ./nixos-enter.sh; + inherit (pkgs) runtimeShell; + }; + +in + +{ + + options.system.nixos-generate-config = { + configuration = mkOption { + internal = true; + type = types.str; + description = '' + The NixOS module that <literal>nixos-generate-config</literal> + saves to <literal>/etc/nixos/configuration.nix</literal>. + + This is an internal option. No backward compatibility is guaranteed. + Use at your own risk! + + Note that this string gets spliced into a Perl script. The perl + variable <literal>$bootLoaderConfig</literal> can be used to + splice in the boot loader configuration. + ''; + }; + + desktopConfiguration = mkOption { + internal = true; + type = types.listOf types.lines; + default = []; + description = '' + Text to preseed the desktop configuration that <literal>nixos-generate-config</literal> + saves to <literal>/etc/nixos/configuration.nix</literal>. + + This is an internal option. No backward compatibility is guaranteed. + Use at your own risk! + + Note that this string gets spliced into a Perl script. The perl + variable <literal>$bootLoaderConfig</literal> can be used to + splice in the boot loader configuration. + ''; + }; + }; + + options.system.disableInstallerTools = mkOption { + internal = true; + type = types.bool; + default = false; + description = '' + Disable nixos-rebuild, nixos-generate-config, nixos-installer + and other NixOS tools. This is useful to shrink embedded, + read-only systems which are not expected to be rebuild or + reconfigure themselves. Use at your own risk! + ''; + }; + + config = lib.mkIf (!config.system.disableInstallerTools) { + + system.nixos-generate-config.configuration = mkDefault '' + # Edit this configuration file to define what should be installed on + # your system. Help is available in the configuration.nix(5) man page + # and in the NixOS manual (accessible by running ‘nixos-help’). + + { config, pkgs, ... }: + + { + imports = + [ # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + $bootLoaderConfig + # networking.hostName = "nixos"; # Define your hostname. + # Pick only one of the below networking options. + # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. + # networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. + + # Set your time zone. + # time.timeZone = "Europe/Amsterdam"; + + # Configure network proxy if necessary + # networking.proxy.default = "http://user:password\@proxy:port/"; + # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; + + # Select internationalisation properties. + # i18n.defaultLocale = "en_US.UTF-8"; + # console = { + # font = "Lat2-Terminus16"; + # keyMap = "us"; + # useXkbConfig = true; # use xkbOptions in tty. + # }; + + $xserverConfig + + $desktopConfiguration + # Configure keymap in X11 + # services.xserver.layout = "us"; + # services.xserver.xkbOptions = { + # "eurosign:e"; + # "caps:escape" # map caps to escape. + # }; + + # Enable CUPS to print documents. + # services.printing.enable = true; + + # Enable sound. + # sound.enable = true; + # hardware.pulseaudio.enable = true; + + # Enable touchpad support (enabled default in most desktopManager). + # services.xserver.libinput.enable = true; + + # Define a user account. Don't forget to set a password with ‘passwd’. + # users.users.jane = { + # isNormalUser = true; + # extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user. + # }; + + # List packages installed in system profile. To search, run: + # \$ nix search wget + # environment.systemPackages = with pkgs; [ + # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. + # wget + # firefox + # ]; + + # Some programs need SUID wrappers, can be configured further or are + # started in user sessions. + # programs.mtr.enable = true; + # programs.gnupg.agent = { + # enable = true; + # enableSSHSupport = true; + # }; + + # List services that you want to enable: + + # Enable the OpenSSH daemon. + # services.openssh.enable = true; + + # Open ports in the firewall. + # networking.firewall.allowedTCPPorts = [ ... ]; + # networking.firewall.allowedUDPPorts = [ ... ]; + # Or disable the firewall altogether. + # networking.firewall.enable = false; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment? + + } + ''; + + environment.systemPackages = + [ nixos-build-vms + nixos-install + nixos-rebuild + nixos-generate-config + nixos-version + nixos-enter + ] ++ lib.optional (nixos-option != null) nixos-option; + + system.build = { + inherit nixos-install nixos-generate-config nixos-option nixos-rebuild nixos-enter; + }; + + }; + +} |