Merge pull request #23026 from copumpkin/nixos-install-wip

Refactor nixos-install to separate out filesystem build logic

authored by Daniel Peebles and committed by GitHub e9f1d869 5a7b029f

+164 -130
+30 -123
nixos/modules/installer/tools/nixos-install.sh
··· 87 87 exit 1 88 88 fi 89 89 90 - 91 - # Mount some stuff in the target root directory. 92 - mkdir -m 0755 -p $mountPoint/dev $mountPoint/proc $mountPoint/sys $mountPoint/etc $mountPoint/run $mountPoint/home 93 - mkdir -m 01777 -p $mountPoint/tmp 94 - mkdir -m 0755 -p $mountPoint/tmp/root 95 - mkdir -m 0755 -p $mountPoint/var 96 - mkdir -m 0700 -p $mountPoint/root 97 - mount --rbind /dev $mountPoint/dev 98 - mount --rbind /proc $mountPoint/proc 99 - mount --rbind /sys $mountPoint/sys 100 - mount --rbind / $mountPoint/tmp/root 101 - mount -t tmpfs -o "mode=0755" none $mountPoint/run 102 - rm -rf $mountPoint/var/run 103 - ln -s /run $mountPoint/var/run 104 - for f in /etc/resolv.conf /etc/hosts; do rm -f $mountPoint/$f; [ -f "$f" ] && cp -Lf $f $mountPoint/etc/; done 105 - for f in /etc/passwd /etc/group; do touch $mountPoint/$f; [ -f "$f" ] && mount --rbind -o ro $f $mountPoint/$f; done 106 - 107 - cp -Lf "@cacert@" "$mountPoint/tmp/ca-cert.crt" 108 - export SSL_CERT_FILE=/tmp/ca-cert.crt 109 - # For Nix 1.7 110 - export CURL_CA_BUNDLE=/tmp/ca-cert.crt 111 - 112 - if [ -n "$runChroot" ]; then 113 - if ! [ -L $mountPoint/nix/var/nix/profiles/system ]; then 114 - echo "$0: installation not finished; cannot chroot into installation directory" 115 - exit 1 116 - fi 117 - ln -s /nix/var/nix/profiles/system $mountPoint/run/current-system 118 - exec chroot $mountPoint "${chrootCommand[@]}" 119 - fi 120 - 121 - 122 90 # Get the path of the NixOS configuration file. 123 91 if test -z "$NIXOS_CONFIG"; then 124 92 NIXOS_CONFIG=/etc/nixos/configuration.nix ··· 130 98 fi 131 99 132 100 133 - # Create the necessary Nix directories on the target device, if they 134 - # don't already exist. 135 - mkdir -m 0755 -p \ 136 - $mountPoint/nix/var/nix/gcroots \ 137 - $mountPoint/nix/var/nix/temproots \ 138 - $mountPoint/nix/var/nix/userpool \ 139 - $mountPoint/nix/var/nix/profiles \ 140 - $mountPoint/nix/var/nix/db \ 141 - $mountPoint/nix/var/log/nix/drvs 142 - 143 - mkdir -m 1775 -p $mountPoint/nix/store 144 - chown @root_uid@:@nixbld_gid@ $mountPoint/nix/store 145 - 146 - 147 - # There is no daemon in the chroot. 148 - unset NIX_REMOTE 149 - 150 - 151 - # We don't have locale-archive in the chroot, so clear $LANG. 152 - export LANG= 153 - export LC_ALL= 154 - export LC_TIME= 155 - 156 - 157 101 # Builds will use users that are members of this group 158 102 extraBuildFlags+=(--option "build-users-group" "$buildUsersGroup") 159 103 160 - 161 104 # Inherit binary caches from the host 105 + # TODO: will this still work with Nix 1.12 now that it has no perl? Probably not... 162 106 binary_caches="$(@perl@/bin/perl -I @nix@/lib/perl5/site_perl/*/* -e 'use Nix::Config; Nix::Config::readConfig; print $Nix::Config::config{"binary-caches"};')" 163 107 extraBuildFlags+=(--option "binary-caches" "$binary_caches") 164 108 109 + nixpkgs="$(readlink -f "$(nix-instantiate --find-file nixpkgs)")" 110 + export NIX_PATH="nixpkgs=$nixpkgs:nixos-config=$mountPoint/$NIXOS_CONFIG" 111 + unset NIXOS_CONFIG 165 112 166 - # Copy Nix to the Nix store on the target device, unless it's already there. 167 - if ! NIX_DB_DIR=$mountPoint/nix/var/nix/db nix-store --check-validity @nix@ 2> /dev/null; then 168 - echo "copying Nix to $mountPoint...." 169 - for i in $(@perl@/bin/perl @pathsFromGraph@ @nixClosure@); do 170 - echo " $i" 171 - chattr -R -i $mountPoint/$i 2> /dev/null || true # clear immutable bit 172 - @rsync@/bin/rsync -a $i $mountPoint/nix/store/ 173 - done 113 + # TODO: do I need to set NIX_SUBSTITUTERS here or is the --option binary-caches above enough? 174 114 175 - # Register the paths in the Nix closure as valid. This is necessary 176 - # to prevent them from being deleted the first time we install 177 - # something. (I.e., Nix will see that, e.g., the glibc path is not 178 - # valid, delete it to get it out of the way, but as a result nothing 179 - # will work anymore.) 180 - chroot $mountPoint @nix@/bin/nix-store --register-validity < @nixClosure@ 181 - fi 182 115 183 - 184 - # Create the required /bin/sh symlink; otherwise lots of things 185 - # (notably the system() function) won't work. 186 - mkdir -m 0755 -p $mountPoint/bin 187 - # !!! assuming that @shell@ is in the closure 188 - ln -sf @shell@ $mountPoint/bin/sh 189 - 116 + # A place to drop temporary closures 117 + trap "rm -rf $tmpdir" EXIT 118 + tmpdir="$(mktemp -d)" 190 119 191 - # Build hooks likely won't function correctly in the minimal chroot; just disable them. 192 - unset NIX_BUILD_HOOK 193 - 194 - # Make the build below copy paths from the CD if possible. Note that 195 - # /tmp/root in the chroot is the root of the CD. 196 - export NIX_OTHER_STORES=/tmp/root/nix:$NIX_OTHER_STORES 197 - 198 - p=@nix@/libexec/nix/substituters 199 - export NIX_SUBSTITUTERS=$p/copy-from-other-stores.pl:$p/download-from-binary-cache.pl 120 + # Build a closure (on the host; we then copy it into the guest) 121 + function closure() { 122 + nix-build "${extraBuildFlags[@]}" --no-out-link -E "with import <nixpkgs> {}; runCommand \"closure\" { exportReferencesGraph = [ \"x\" (buildEnv { name = \"env\"; paths = [ ($1) stdenv ]; }) ]; } \"cp x \$out\"" 123 + } 200 124 125 + system_closure="$tmpdir/system.closure" 201 126 202 127 if [ -z "$closure" ]; then 203 - # Get the absolute path to the NixOS/Nixpkgs sources. 204 - nixpkgs="$(readlink -f $(nix-instantiate --find-file nixpkgs))" 205 - 206 - nixEnvAction="-f <nixpkgs/nixos> --set -A system" 128 + expr="(import <nixpkgs/nixos> {}).system" 129 + system_root="$(nix-build -E "$expr")" 130 + system_closure="$(closure "$expr")" 207 131 else 208 - nixpkgs="" 209 - nixEnvAction="--set $closure" 132 + system_root=$closure 133 + # Create a temporary file ending in .closure (so nixos-prepare-root knows to --import it) to transport the store closure 134 + # to the filesytem we're preparing. Also delete it on exit! 135 + nix-store --export $(nix-store -qR $closure) > $system_closure 210 136 fi 211 137 212 - # Build the specified Nix expression in the target store and install 213 - # it into the system configuration profile. 214 - echo "building the system configuration..." 215 - NIX_PATH="nixpkgs=/tmp/root/$nixpkgs:nixos-config=$NIXOS_CONFIG" NIXOS_CONFIG= \ 216 - chroot $mountPoint @nix@/bin/nix-env \ 217 - "${extraBuildFlags[@]}" -p /nix/var/nix/profiles/system $nixEnvAction 218 - 219 - 220 - # Copy the NixOS/Nixpkgs sources to the target as the initial contents 221 - # of the NixOS channel. 222 - mkdir -m 0755 -p $mountPoint/nix/var/nix/profiles 223 - mkdir -m 1777 -p $mountPoint/nix/var/nix/profiles/per-user 224 - mkdir -m 0755 -p $mountPoint/nix/var/nix/profiles/per-user/root 225 - srcs=$(nix-env "${extraBuildFlags[@]}" -p /nix/var/nix/profiles/per-user/root/channels -q nixos --no-name --out-path 2>/dev/null || echo -n "") 226 - if [ -z "$noChannelCopy" ] && [ -n "$srcs" ]; then 227 - echo "copying NixOS/Nixpkgs sources..." 228 - chroot $mountPoint @nix@/bin/nix-env \ 229 - "${extraBuildFlags[@]}" -p /nix/var/nix/profiles/per-user/root/channels -i "$srcs" --quiet 230 - fi 231 - mkdir -m 0700 -p $mountPoint/root/.nix-defexpr 232 - ln -sfn /nix/var/nix/profiles/per-user/root/channels $mountPoint/root/.nix-defexpr/channels 138 + channel_root="$(nix-env -p /nix/var/nix/profiles/per-user/root/channels -q nixos --no-name --out-path 2>/dev/null || echo -n "")" 139 + channel_closure="$tmpdir/channel.closure" 140 + nix-store --export $channel_root > $channel_closure 233 141 142 + # Populate the target root directory with the basics 143 + @prepare_root@/bin/nixos-prepare-root $mountPoint $channel_root $system_root @nixClosure@ $system_closure $channel_closure 234 144 235 - # Get rid of the /etc bind mounts. 236 - for f in /etc/passwd /etc/group; do [ -f "$f" ] && umount $mountPoint/$f; done 145 + # nixos-prepare-root doesn't currently do anything with file ownership, so we set it up here instead 146 + chown @root_uid@:@nixbld_gid@ $mountPoint/nix/store 237 147 148 + mount --rbind /dev $mountPoint/dev 149 + mount --rbind /proc $mountPoint/proc 150 + mount --rbind /sys $mountPoint/sys 238 151 239 152 # Grub needs an mtab. 240 153 ln -sfn /proc/mounts $mountPoint/etc/mtab 241 - 242 - 243 - # Mark the target as a NixOS installation, otherwise 244 - # switch-to-configuration will chicken out. 245 - touch $mountPoint/etc/NIXOS 246 - 247 154 248 155 # Switch to the new system configuration. This will install Grub with 249 156 # a menu default pointing at the kernel/initrd/etc of the new
+105
nixos/modules/installer/tools/nixos-prepare-root.sh
··· 1 + #! @shell@ 2 + 3 + # This script's goal is to perform all "static" setup of a filesystem structure from pre-built store paths. Everything 4 + # in here should run in a non-root context and inside a Nix builder. It's designed primarily to be called from image- 5 + # building scripts and from nixos-install, but because it makes very few assumptions about the context in which it runs, 6 + # it could be useful in other contexts as well. 7 + # 8 + # Current behavior: 9 + # - set up basic filesystem structure 10 + # - make Nix store etc. 11 + # - copy Nix, system, channel, and misceallaneous closures to target Nix store 12 + # - register validity of all paths in the target store 13 + # - set up channel and system profiles 14 + 15 + # Ensure a consistent umask. 16 + umask 0022 17 + 18 + set -e 19 + 20 + mountPoint="$1" 21 + channel="$2" 22 + system="$3" 23 + shift 3 24 + closures="$@" 25 + 26 + PATH="@coreutils@/bin:@nix@/bin:@perl@/bin:@utillinux@/bin:@rsync@/bin" 27 + 28 + if ! test -e "$mountPoint"; then 29 + echo "mount point $mountPoint doesn't exist" 30 + exit 1 31 + fi 32 + 33 + # Create a few of the standard directories in the target root directory. 34 + mkdir -m 0755 -p $mountPoint/dev $mountPoint/proc $mountPoint/sys $mountPoint/etc $mountPoint/run $mountPoint/home 35 + mkdir -m 01777 -p $mountPoint/tmp 36 + mkdir -m 0755 -p $mountPoint/tmp/root 37 + mkdir -m 0755 -p $mountPoint/var 38 + mkdir -m 0700 -p $mountPoint/root 39 + 40 + ln -s /run $mountPoint/var/run 41 + 42 + # Create the necessary Nix directories on the target device 43 + mkdir -m 0755 -p \ 44 + $mountPoint/nix/var/nix/gcroots \ 45 + $mountPoint/nix/var/nix/temproots \ 46 + $mountPoint/nix/var/nix/userpool \ 47 + $mountPoint/nix/var/nix/profiles \ 48 + $mountPoint/nix/var/nix/db \ 49 + $mountPoint/nix/var/log/nix/drvs 50 + 51 + mkdir -m 1775 -p $mountPoint/nix/store 52 + 53 + # All Nix operations below should operate on our target store, not /nix/store. 54 + # N.B: this relies on Nix 1.12 or higher 55 + export NIX_REMOTE=local?root=$mountPoint 56 + 57 + # Copy our closures to the Nix store on the target mount point, unless they're already there. 58 + for i in $closures; do 59 + # We support closures both in the format produced by `nix-store --export` and by `exportReferencesGraph`, 60 + # mostly because there doesn't seem to be a single format that can be produced outside of a nix build and 61 + # inside one. See https://github.com/NixOS/nix/issues/1242 for more discussion. 62 + if [[ "$i" =~ \.closure$ ]]; then 63 + echo "importing serialized closure $i to $mountPoint..." 64 + nix-store --import < $i 65 + else 66 + # There has to be a better way to do this, right? 67 + echo "copying closure $i to $mountPoint..." 68 + for j in $(perl @pathsFromGraph@ $i); do 69 + echo " $j... " 70 + rsync -a $j $mountPoint/nix/store/ 71 + done 72 + 73 + nix-store --register-validity < $i 74 + fi 75 + done 76 + 77 + # Create the required /bin/sh symlink; otherwise lots of things 78 + # (notably the system() function) won't work. 79 + if [ ! -x $mountPoint/@shell@ ]; then 80 + echo "Error: @shell@ wasn't included in the closure" >&2 81 + exit 1 82 + fi 83 + mkdir -m 0755 -p $mountPoint/bin 84 + ln -sf @shell@ $mountPoint/bin/sh 85 + 86 + echo "setting the system closure to '$system'..." 87 + nix-env "${extraBuildFlags[@]}" -p $mountPoint/nix/var/nix/profiles/system --set "$system" 88 + 89 + ln -sfn /nix/var/nix/profiles/system $mountPoint/run/current-system 90 + 91 + # Copy the NixOS/Nixpkgs sources to the target as the initial contents of the NixOS channel. 92 + mkdir -m 0755 -p $mountPoint/nix/var/nix/profiles 93 + mkdir -m 1777 -p $mountPoint/nix/var/nix/profiles/per-user 94 + mkdir -m 0755 -p $mountPoint/nix/var/nix/profiles/per-user/root 95 + 96 + if [ -z "$noChannelCopy" ] && [ -n "$channel" ]; then 97 + echo "copying channel..." 98 + nix-env --option build-use-substitutes false "${extraBuildFlags[@]}" -p $mountPoint/nix/var/nix/profiles/per-user/root/channels --set "$channel" --quiet 99 + fi 100 + mkdir -m 0700 -p $mountPoint/root/.nix-defexpr 101 + ln -sfn /nix/var/nix/profiles/per-user/root/channels $mountPoint/root/.nix-defexpr/channels 102 + 103 + # Mark the target as a NixOS installation, otherwise switch-to-configuration will chicken out. 104 + touch $mountPoint/etc/NIXOS 105 +
+11 -2
nixos/modules/installer/tools/tools.nix
··· 4 4 { config, pkgs, modulesPath, ... }: 5 5 6 6 let 7 - 8 7 cfg = config.installer; 9 8 10 9 makeProg = args: pkgs.substituteAll (args // { ··· 17 16 src = ./nixos-build-vms/nixos-build-vms.sh; 18 17 }; 19 18 19 + nixos-prepare-root = makeProg { 20 + name = "nixos-prepare-root"; 21 + src = ./nixos-prepare-root.sh; 22 + 23 + nix = pkgs.nixUnstable; 24 + inherit (pkgs) perl pathsFromGraph rsync utillinux coreutils; 25 + }; 26 + 20 27 nixos-install = makeProg { 21 28 name = "nixos-install"; 22 29 src = ./nixos-install.sh; ··· 26 33 cacert = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; 27 34 root_uid = config.ids.uids.root; 28 35 nixbld_gid = config.ids.gids.nixbld; 36 + prepare_root = nixos-prepare-root; 29 37 30 38 nixClosure = pkgs.runCommand "closure" 31 39 { exportReferencesGraph = ["refs" config.nix.package.out]; } ··· 69 77 70 78 environment.systemPackages = 71 79 [ nixos-build-vms 80 + nixos-prepare-root 72 81 nixos-install 73 82 nixos-rebuild 74 83 nixos-generate-config ··· 77 86 ]; 78 87 79 88 system.build = { 80 - inherit nixos-install nixos-generate-config nixos-option nixos-rebuild; 89 + inherit nixos-install nixos-prepare-root nixos-generate-config nixos-option nixos-rebuild; 81 90 }; 82 91 83 92 };
+15 -3
nixos/tests/installer.nix
··· 34 34 boot.loader.systemd-boot.enable = true; 35 35 ''} 36 36 37 + users.extraUsers.alice = { 38 + isNormalUser = true; 39 + home = "/home/alice"; 40 + description = "Alice Foobar"; 41 + }; 42 + 37 43 hardware.enableAllFirmware = lib.mkForce false; 38 44 39 45 ${replaceChars ["\n"] ["\n "] extraConfig} ··· 96 102 $machine->shutdown; 97 103 98 104 # Now see if we can boot the installation. 99 - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}" }); 105 + $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "boot-after-install" }); 100 106 101 107 # For example to enter LUKS passphrase. 102 108 ${preBootCommands} ··· 118 124 $machine->waitForUnit("swap.target"); 119 125 $machine->succeed("cat /proc/swaps | grep -q /dev"); 120 126 127 + # Check that the store is in good shape 128 + $machine->succeed("nix-store --verify --check-contents >&2"); 129 + 121 130 # Check whether the channel works. 122 131 $machine->succeed("nix-env -iA nixos.procps >&2"); 123 132 $machine->succeed("type -tP ps | tee /dev/stderr") =~ /.nix-profile/ 124 133 or die "nix-env failed"; 134 + 135 + # Check that the daemon works, and that non-root users can run builds (this will build a new profile generation through the daemon) 136 + $machine->succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2"); 125 137 126 138 # We need to a writable nix-store on next boot. 127 139 $machine->copyFileFromHost( ··· 139 151 $machine->shutdown; 140 152 141 153 # Check whether a writable store build works 142 - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}" }); 154 + $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "rebuild-switch" }); 143 155 ${preBootCommands} 144 156 $machine->waitForUnit("multi-user.target"); 145 157 $machine->copyFileFromHost( ··· 150 162 151 163 # And just to be sure, check that the machine still boots after 152 164 # "nixos-rebuild switch". 153 - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}" }); 165 + $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", "boot-after-rebuild-switch" }); 154 166 ${preBootCommands} 155 167 $machine->waitForUnit("network.target"); 156 168 $machine->shutdown;
+3 -2
pkgs/tools/package-management/nix/default.nix
··· 131 131 sha256 = "69e0f398affec2a14c47b46fec712906429c85312d5483be43e4c34da4f63f67"; 132 132 }; 133 133 134 - # 1.11.8 doesn't yet have the patch to work on LLVM 4, so we patch it for now. Take this out once 135 - # we move to a higher version. I'd pull the specific patch from upstream but it doesn't apply cleanly. 134 + # Until 1.11.9 is released, we do this :) 136 135 patchPhase = '' 137 136 substituteInPlace src/libexpr/json-to-value.cc \ 138 137 --replace 'std::less<Symbol>, gc_allocator<Value *>' \ 139 138 'std::less<Symbol>, gc_allocator<std::pair<const Symbol, Value *> >' 139 + 140 + sed -i '/if (settings.readOnlyMode) {/a curSchema = getSchema();' src/libstore/local-store.cc 140 141 ''; 141 142 }) // { perl-bindings = nixStable; }; 142 143