Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at python-updates 1414 lines 48 kB view raw
1{ 2 bashInteractive, 3 buildPackages, 4 cacert, 5 callPackage, 6 closureInfo, 7 coreutils, 8 devShellTools, 9 e2fsprogs, 10 proot, 11 fakeNss, 12 fakeroot, 13 file, 14 go, 15 jq, 16 jshon, 17 lib, 18 makeWrapper, 19 moreutils, 20 nix, 21 nixosTests, 22 pigz, 23 rsync, 24 runCommand, 25 runtimeShell, 26 shadow, 27 skopeo, 28 stdenv, 29 storeDir ? builtins.storeDir, 30 symlinkJoin, 31 tarsum, 32 util-linux, 33 vmTools, 34 writeClosure, 35 writeScript, 36 writeShellScriptBin, 37 writeText, 38 writeTextDir, 39 writePython3, 40 zstd, 41}: 42 43let 44 inherit (lib) 45 optionals 46 optionalString 47 ; 48 49 inherit (lib) 50 escapeShellArgs 51 toList 52 ; 53 54 inherit (devShellTools) 55 valueToString 56 ; 57 58 mkDbExtraCommand = 59 contents: 60 let 61 contentsList = if builtins.isList contents then contents else [ contents ]; 62 in 63 '' 64 echo "Generating the nix database..." 65 echo "Warning: only the database of the deepest Nix layer is loaded." 66 echo " If you want to use nix commands in the container, it would" 67 echo " be better to only have one layer that contains a nix store." 68 69 export NIX_REMOTE=local?root=$PWD 70 # A user is required by nix 71 # https://github.com/NixOS/nix/blob/9348f9291e5d9e4ba3c4347ea1b235640f54fd79/src/libutil/util.cc#L478 72 export USER=nobody 73 ${buildPackages.nix}/bin/nix-store --load-db < ${ 74 closureInfo { rootPaths = contentsList; } 75 }/registration 76 # Reset registration times to make the image reproducible 77 ${buildPackages.sqlite}/bin/sqlite3 nix/var/nix/db/db.sqlite "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}" 78 79 mkdir -p nix/var/nix/gcroots/docker/ 80 for i in ${lib.concatStringsSep " " contentsList}; do 81 ln -s $i nix/var/nix/gcroots/docker/$(basename $i) 82 done; 83 ''; 84 85 # The OCI Image specification recommends that configurations use values listed 86 # in the Go Language document for GOARCH. 87 # Reference: https://github.com/opencontainers/image-spec/blob/master/config.md#properties 88 # For the mapping from Nixpkgs system parameters to GOARCH, we can reuse the 89 # mapping from the go package. 90 defaultArchitecture = go.GOARCH; 91 92 compressors = { 93 none = { 94 ext = ""; 95 nativeInputs = [ ]; 96 compress = "cat"; 97 decompress = "cat"; 98 }; 99 gz = { 100 ext = ".gz"; 101 nativeInputs = [ pigz ]; 102 compress = "pigz -p$NIX_BUILD_CORES -nTR"; 103 decompress = "pigz -d -p$NIX_BUILD_CORES"; 104 }; 105 zstd = { 106 ext = ".zst"; 107 nativeInputs = [ zstd ]; 108 compress = "zstd -T$NIX_BUILD_CORES"; 109 decompress = "zstd -d -T$NIX_BUILD_CORES"; 110 }; 111 }; 112 113 compressorForImage = 114 compressor: imageName: 115 compressors.${compressor} 116 or (throw "in docker image ${imageName}: compressor must be one of: [${toString builtins.attrNames compressors}]"); 117 118in 119rec { 120 examples = callPackage ./examples.nix { 121 inherit 122 buildImage 123 buildLayeredImage 124 fakeNss 125 pullImage 126 shadowSetup 127 buildImageWithNixDb 128 streamNixShellImage 129 ; 130 }; 131 132 tests = { 133 inherit (nixosTests) 134 docker-tools 135 docker-tools-overlay 136 # requires remote builder 137 # docker-tools-cross 138 ; 139 }; 140 141 pullImage = 142 let 143 fixName = name: builtins.replaceStrings [ "/" ":" ] [ "-" "-" ] name; 144 in 145 lib.fetchers.withNormalizedHash { } ( 146 { 147 imageName, 148 # To find the digest of an image, you can use skopeo: 149 # see doc/functions.xml 150 imageDigest, 151 outputHash, 152 outputHashAlgo, 153 os ? "linux", 154 # Image architecture, defaults to the architecture of the `hostPlatform` when unset 155 arch ? defaultArchitecture, 156 # This is used to set name to the pulled image 157 finalImageName ? imageName, 158 # This used to set a tag to the pulled image 159 finalImageTag ? "latest", 160 # This is used to disable TLS certificate verification, allowing access to http registries on (hopefully) trusted networks 161 tlsVerify ? true, 162 163 name ? fixName "docker-image-${finalImageName}-${finalImageTag}.tar", 164 }: 165 166 runCommand name 167 { 168 inherit imageDigest; 169 imageName = finalImageName; 170 imageTag = finalImageTag; 171 impureEnvVars = lib.fetchers.proxyImpureEnvVars; 172 173 inherit outputHash outputHashAlgo; 174 outputHashMode = "flat"; 175 176 nativeBuildInputs = [ skopeo ]; 177 SSL_CERT_FILE = "${cacert.out}/etc/ssl/certs/ca-bundle.crt"; 178 179 sourceURL = "docker://${imageName}@${imageDigest}"; 180 destNameTag = "${finalImageName}:${finalImageTag}"; 181 } 182 '' 183 skopeo \ 184 --insecure-policy \ 185 --tmpdir=$TMPDIR \ 186 --override-os ${os} \ 187 --override-arch ${arch} \ 188 copy \ 189 --src-tls-verify=${lib.boolToString tlsVerify} \ 190 "$sourceURL" "docker-archive://$out:$destNameTag" \ 191 | cat # pipe through cat to force-disable progress bar 192 '' 193 ); 194 195 # We need to sum layer.tar, not a directory, hence tarsum instead of nix-hash. 196 # And we cannot untar it, because then we cannot preserve permissions etc. 197 inherit tarsum; # pkgs.dockerTools.tarsum 198 199 # buildEnv creates symlinks to dirs, which is hard to edit inside the overlay VM 200 mergeDrvs = 201 { 202 derivations, 203 onlyDeps ? false, 204 }: 205 runCommand "merge-drvs" 206 { 207 inherit derivations onlyDeps; 208 } 209 '' 210 if [[ -n "$onlyDeps" ]]; then 211 echo $derivations > $out 212 exit 0 213 fi 214 215 mkdir $out 216 for derivation in $derivations; do 217 echo "Merging $derivation..." 218 if [[ -d "$derivation" ]]; then 219 # If it's a directory, copy all of its contents into $out. 220 cp -drf --preserve=mode -f $derivation/* $out/ 221 else 222 # Otherwise treat the derivation as a tarball and extract it 223 # into $out. 224 tar -C $out -xpf $drv || true 225 fi 226 done 227 ''; 228 229 # Helper for setting up the base files for managing users and 230 # groups, only if such files don't exist already. It is suitable for 231 # being used in a runAsRoot script. 232 shadowSetup = '' 233 export PATH=${shadow}/bin:$PATH 234 mkdir -p /etc/pam.d 235 if [[ ! -f /etc/passwd ]]; then 236 echo "root:x:0:0::/root:${runtimeShell}" > /etc/passwd 237 echo "root:!x:::::::" > /etc/shadow 238 fi 239 if [[ ! -f /etc/group ]]; then 240 echo "root:x:0:" > /etc/group 241 echo "root:x::" > /etc/gshadow 242 fi 243 if [[ ! -f /etc/pam.d/other ]]; then 244 cat > /etc/pam.d/other <<EOF 245 account sufficient pam_unix.so 246 auth sufficient pam_rootok.so 247 password requisite pam_unix.so nullok yescrypt 248 session required pam_unix.so 249 EOF 250 fi 251 if [[ ! -f /etc/login.defs ]]; then 252 touch /etc/login.defs 253 fi 254 ''; 255 256 # Run commands in a virtual machine. 257 runWithOverlay = 258 { 259 name, 260 fromImage ? null, 261 fromImageName ? null, 262 fromImageTag ? null, 263 diskSize ? 1024, 264 buildVMMemorySize ? 512, 265 preMount ? "", 266 postMount ? "", 267 postUmount ? "", 268 }: 269 vmTools.runInLinuxVM ( 270 runCommand name 271 { 272 preVM = vmTools.createEmptyImage { 273 size = diskSize; 274 fullName = "docker-run-disk"; 275 destination = "./image"; 276 }; 277 inherit fromImage fromImageName fromImageTag; 278 memSize = buildVMMemorySize; 279 280 nativeBuildInputs = [ 281 util-linux 282 e2fsprogs 283 jshon 284 rsync 285 jq 286 ]; 287 } 288 '' 289 mkdir disk 290 mkfs /dev/${vmTools.hd} 291 mount /dev/${vmTools.hd} disk 292 cd disk 293 294 function dedup() { 295 declare -A seen 296 while read ln; do 297 if [[ -z "''${seen["$ln"]:-}" ]]; then 298 echo "$ln"; seen["$ln"]=1 299 fi 300 done 301 } 302 303 if [[ -n "$fromImage" ]]; then 304 echo "Unpacking base image..." 305 mkdir image 306 tar -C image -xpf "$fromImage" 307 308 if [[ -n "$fromImageName" ]] && [[ -n "$fromImageTag" ]]; then 309 parentID="$( 310 cat "image/manifest.json" | 311 jq -r '.[] | select(.RepoTags | contains([$desiredTag])) | rtrimstr(".json")' \ 312 --arg desiredTag "$fromImageName:$fromImageTag" 313 )" 314 else 315 echo "From-image name or tag wasn't set. Reading the first ID." 316 parentID="$(cat "image/manifest.json" | jq -r '.[0].Config | rtrimstr(".json")')" 317 fi 318 319 # In case of repeated layers, unpack only the last occurrence of each 320 cat ./image/manifest.json | jq -r '.[0].Layers | .[]' | tac | dedup | tac > layer-list 321 else 322 touch layer-list 323 fi 324 325 # Unpack all of the parent layers into the image. 326 lowerdir="" 327 extractionID=0 328 for layerTar in $(cat layer-list); do 329 echo "Unpacking layer $layerTar" 330 extractionID=$((extractionID + 1)) 331 332 mkdir -p image/$extractionID/layer 333 tar -C image/$extractionID/layer -xpf image/$layerTar 334 rm image/$layerTar 335 336 find image/$extractionID/layer -name ".wh.*" -exec bash -c 'name="$(basename {}|sed "s/^.wh.//")"; mknod "$(dirname {})/$name" c 0 0; rm {}' \; 337 338 # Get the next lower directory and continue the loop. 339 lowerdir=image/$extractionID/layer''${lowerdir:+:}$lowerdir 340 done 341 342 mkdir work 343 mkdir layer 344 mkdir mnt 345 346 ${lib.optionalString (preMount != "") '' 347 # Execute pre-mount steps 348 echo "Executing pre-mount steps..." 349 ${preMount} 350 ''} 351 352 if [ -n "$lowerdir" ]; then 353 mount -t overlay overlay -olowerdir=$lowerdir,workdir=work,upperdir=layer mnt 354 else 355 mount --bind layer mnt 356 fi 357 358 ${lib.optionalString (postMount != "") '' 359 # Execute post-mount steps 360 echo "Executing post-mount steps..." 361 ${postMount} 362 ''} 363 364 umount mnt 365 366 ( 367 cd layer 368 cmd='name="$(basename {})"; touch "$(dirname {})/.wh.$name"; rm "{}"' 369 find . -type c -exec bash -c "$cmd" \; 370 ) 371 372 ${postUmount} 373 '' 374 ); 375 376 exportImage = 377 { 378 name ? fromImage.name, 379 fromImage, 380 fromImageName ? null, 381 fromImageTag ? null, 382 diskSize ? 1024, 383 }: 384 runWithOverlay { 385 inherit 386 name 387 fromImage 388 fromImageName 389 fromImageTag 390 diskSize 391 ; 392 393 postMount = '' 394 echo "Packing raw image..." 395 mkdir -p $out 396 tar -C mnt --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" -cf $out/layer.tar . 397 ''; 398 399 postUmount = '' 400 mv $out/layer.tar . 401 rm -rf $out 402 mv layer.tar $out 403 ''; 404 }; 405 406 # Create an executable shell script which has the coreutils in its 407 # PATH. Since root scripts are executed in a blank environment, even 408 # things like `ls` or `echo` will be missing. 409 shellScript = 410 name: text: 411 writeScript name '' 412 #!${runtimeShell} 413 set -e 414 export PATH=${coreutils}/bin:/bin 415 ${text} 416 ''; 417 418 # Create a "layer" (set of files). 419 mkPureLayer = 420 { 421 # Name of the layer 422 name, 423 # JSON containing configuration and metadata for this layer. 424 baseJson, 425 # Files to add to the layer. 426 copyToRoot ? null, 427 # When copying the contents into the image, preserve symlinks to 428 # directories (see `rsync -K`). Otherwise, transform those symlinks 429 # into directories. 430 keepContentsDirlinks ? false, 431 # Additional commands to run on the layer before it is tar'd up. 432 extraCommands ? "", 433 uid ? 0, 434 gid ? 0, 435 }: 436 runCommand "docker-layer-${name}" 437 { 438 inherit baseJson extraCommands; 439 contents = copyToRoot; 440 nativeBuildInputs = [ 441 jshon 442 rsync 443 tarsum 444 ]; 445 } 446 '' 447 mkdir layer 448 if [[ -n "$contents" ]]; then 449 echo "Adding contents..." 450 for item in $contents; do 451 echo "Adding $item" 452 rsync -a${if keepContentsDirlinks then "K" else "k"} --chown=0:0 $item/ layer/ 453 done 454 else 455 echo "No contents to add to layer." 456 fi 457 458 chmod ug+w layer 459 460 if [[ -n "$extraCommands" ]]; then 461 (cd layer; eval "$extraCommands") 462 fi 463 464 # Tar up the layer and throw it into 'layer.tar'. 465 echo "Packing layer..." 466 mkdir $out 467 tarhash=$(tar -C layer --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=${toString uid} --group=${toString gid} -cf - . | tee -p $out/layer.tar | tarsum) 468 469 # Add a 'checksum' field to the JSON, with the value set to the 470 # checksum of the tarball. 471 cat ${baseJson} | jshon -s "$tarhash" -i checksum > $out/json 472 473 # Indicate to docker that we're using schema version 1.0. 474 echo -n "1.0" > $out/VERSION 475 476 echo "Finished building layer '${name}'" 477 ''; 478 479 # Make a "root" layer; required if we need to execute commands as a 480 # privileged user on the image. The commands themselves will be 481 # performed in a virtual machine sandbox. 482 mkRootLayer = 483 { 484 # Name of the image. 485 name, 486 # Script to run as root. Bash. 487 runAsRoot, 488 # Files to add to the layer. If null, an empty layer will be created. 489 # To add packages to /bin, use `buildEnv` or similar. 490 copyToRoot ? null, 491 # When copying the contents into the image, preserve symlinks to 492 # directories (see `rsync -K`). Otherwise, transform those symlinks 493 # into directories. 494 keepContentsDirlinks ? false, 495 # JSON containing configuration and metadata for this layer. 496 baseJson, 497 # Existing image onto which to append the new layer. 498 fromImage ? null, 499 # Name of the image we're appending onto. 500 fromImageName ? null, 501 # Tag of the image we're appending onto. 502 fromImageTag ? null, 503 # How much disk to allocate for the temporary virtual machine. 504 diskSize ? 1024, 505 # How much memory to allocate for the temporary virtual machine. 506 buildVMMemorySize ? 512, 507 # Commands (bash) to run on the layer; these do not require sudo. 508 extraCommands ? "", 509 }: 510 # Generate an executable script from the `runAsRoot` text. 511 let 512 runAsRootScript = shellScript "run-as-root.sh" runAsRoot; 513 extraCommandsScript = shellScript "extra-commands.sh" extraCommands; 514 in 515 runWithOverlay { 516 name = "docker-layer-${name}"; 517 518 inherit 519 fromImage 520 fromImageName 521 fromImageTag 522 diskSize 523 buildVMMemorySize 524 ; 525 526 preMount = lib.optionalString (copyToRoot != null && copyToRoot != [ ]) '' 527 echo "Adding contents..." 528 for item in ${escapeShellArgs (map (c: "${c}") (toList copyToRoot))}; do 529 echo "Adding $item..." 530 rsync -a${if keepContentsDirlinks then "K" else "k"} --chown=0:0 $item/ layer/ 531 done 532 533 chmod ug+w layer 534 ''; 535 536 postMount = '' 537 mkdir -p mnt/{dev,proc,sys,tmp} mnt${storeDir} 538 539 # Mount /dev, /sys and the nix store as shared folders. 540 mount --rbind /dev mnt/dev 541 mount --rbind /sys mnt/sys 542 mount --rbind ${storeDir} mnt${storeDir} 543 544 # Execute the run as root script. See 'man unshare' for 545 # details on what's going on here; basically this command 546 # means that the runAsRootScript will be executed in a nearly 547 # completely isolated environment. 548 # 549 # Ideally we would use --mount-proc=mnt/proc or similar, but this 550 # doesn't work. The workaround is to setup proc after unshare. 551 # See: https://github.com/karelzak/util-linux/issues/648 552 unshare -imnpuf --mount-proc sh -c 'mount --rbind /proc mnt/proc && chroot mnt ${runAsRootScript}' 553 554 # Unmount directories and remove them. 555 umount -R mnt/dev mnt/sys mnt${storeDir} 556 rmdir --ignore-fail-on-non-empty \ 557 mnt/dev mnt/proc mnt/sys mnt${storeDir} \ 558 mnt$(dirname ${storeDir}) 559 ''; 560 561 postUmount = '' 562 (cd layer; ${extraCommandsScript}) 563 564 echo "Packing layer..." 565 mkdir -p $out 566 tarhash=$(tar -C layer --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" -cf - . | 567 tee -p $out/layer.tar | 568 ${tarsum}/bin/tarsum) 569 570 cat ${baseJson} | jshon -s "$tarhash" -i checksum > $out/json 571 # Indicate to docker that we're using schema version 1.0. 572 echo -n "1.0" > $out/VERSION 573 574 echo "Finished building layer '${name}'" 575 ''; 576 }; 577 578 buildLayeredImage = lib.makeOverridable ( 579 { 580 name, 581 compressor ? "gz", 582 ... 583 }@args: 584 let 585 stream = streamLayeredImage (builtins.removeAttrs args [ "compressor" ]); 586 compress = compressorForImage compressor name; 587 in 588 runCommand "${baseNameOf name}.tar${compress.ext}" { 589 inherit (stream) imageName; 590 passthru = { 591 inherit (stream) imageTag; 592 inherit stream; 593 }; 594 nativeBuildInputs = compress.nativeInputs; 595 } "${stream} | ${compress.compress} > $out" 596 ); 597 598 # 1. extract the base image 599 # 2. create the layer 600 # 3. add layer deps to the layer itself, diffing with the base image 601 # 4. compute the layer id 602 # 5. put the layer in the image 603 # 6. repack the image 604 buildImage = lib.makeOverridable ( 605 args@{ 606 # Image name. 607 name, 608 # Image tag, when null then the nix output hash will be used. 609 tag ? null, 610 # Parent image, to append to. 611 fromImage ? null, 612 # Name of the parent image; will be read from the image otherwise. 613 fromImageName ? null, 614 # Tag of the parent image; will be read from the image otherwise. 615 fromImageTag ? null, 616 # Files to put on the image (a nix store path or list of paths). 617 copyToRoot ? null, 618 # When copying the contents into the image, preserve symlinks to 619 # directories (see `rsync -K`). Otherwise, transform those symlinks 620 # into directories. 621 keepContentsDirlinks ? false, 622 # Docker config; e.g. what command to run on the container. 623 config ? null, 624 # Image architecture, defaults to the architecture of the `hostPlatform` when unset 625 architecture ? defaultArchitecture, 626 # Optional bash script to run on the files prior to fixturizing the layer. 627 extraCommands ? "", 628 uid ? 0, 629 gid ? 0, 630 # Optional bash script to run as root on the image when provisioning. 631 runAsRoot ? null, 632 # Size of the virtual machine disk to provision when building the image. 633 diskSize ? 1024, 634 # Size of the virtual machine memory to provision when building the image. 635 buildVMMemorySize ? 512, 636 # Time of creation of the image. 637 created ? "1970-01-01T00:00:01Z", 638 # Compressor to use. One of: none, gz, zstd. 639 compressor ? "gz", 640 # Populate the nix database in the image with the dependencies of `copyToRoot`. 641 includeNixDB ? false, 642 # Deprecated. 643 contents ? null, 644 }: 645 646 let 647 checked = 648 lib.warnIf (contents != null) 649 "in docker image ${name}: The contents parameter is deprecated. Change to copyToRoot if the contents are designed to be copied to the root filesystem, such as when you use `buildEnv` or similar between contents and your packages. Use copyToRoot = buildEnv { ... }; or similar if you intend to add packages to /bin." 650 lib.throwIf 651 (contents != null && copyToRoot != null) 652 "in docker image ${name}: You can not specify both contents and copyToRoot."; 653 654 rootContents = if copyToRoot == null then contents else copyToRoot; 655 656 baseName = baseNameOf name; 657 658 # Create a JSON blob of the configuration. Set the date to unix zero. 659 baseJson = 660 let 661 pure = writeText "${baseName}-config.json" ( 662 builtins.toJSON { 663 inherit created config architecture; 664 preferLocalBuild = true; 665 os = "linux"; 666 } 667 ); 668 impure = 669 runCommand "${baseName}-config.json" 670 { 671 nativeBuildInputs = [ jq ]; 672 preferLocalBuild = true; 673 } 674 '' 675 jq ".created = \"$(TZ=utc date --iso-8601="seconds")\"" ${pure} > $out 676 ''; 677 in 678 if created == "now" then impure else pure; 679 680 compress = compressorForImage compressor name; 681 682 # TODO: add the dependencies of the config json. 683 extraCommandsWithDB = 684 if includeNixDB then (mkDbExtraCommand rootContents) + extraCommands else extraCommands; 685 686 layer = 687 if runAsRoot == null then 688 mkPureLayer { 689 name = baseName; 690 inherit 691 baseJson 692 keepContentsDirlinks 693 uid 694 gid 695 ; 696 extraCommands = extraCommandsWithDB; 697 copyToRoot = rootContents; 698 } 699 else 700 mkRootLayer { 701 name = baseName; 702 inherit 703 baseJson 704 fromImage 705 fromImageName 706 fromImageTag 707 keepContentsDirlinks 708 runAsRoot 709 diskSize 710 buildVMMemorySize 711 ; 712 extraCommands = extraCommandsWithDB; 713 copyToRoot = rootContents; 714 }; 715 result = 716 runCommand "docker-image-${baseName}.tar${compress.ext}" 717 { 718 nativeBuildInputs = [ 719 jshon 720 jq 721 moreutils 722 ] 723 ++ compress.nativeInputs; 724 # Image name must be lowercase 725 imageName = lib.toLower name; 726 imageTag = lib.optionalString (tag != null) tag; 727 inherit fromImage baseJson; 728 layerClosure = writeClosure [ layer ]; 729 passthru.buildArgs = args; 730 passthru.layer = layer; 731 passthru.imageTag = 732 if tag != null then 733 tag 734 else 735 lib.head ( 736 lib.strings.splitString "-" (baseNameOf (builtins.unsafeDiscardStringContext result.outPath)) 737 ); 738 } 739 '' 740 ${lib.optionalString (tag == null) '' 741 outName="$(basename "$out")" 742 outHash=$(echo "$outName" | cut -d - -f 1) 743 744 imageTag=$outHash 745 ''} 746 747 # Print tar contents: 748 # 1: Interpreted as relative to the root directory 749 # 2: With no trailing slashes on directories 750 # This is useful for ensuring that the output matches the 751 # values generated by the "find" command 752 ls_tar() { 753 for f in $(tar -tf $1 | xargs realpath -ms --relative-to=.); do 754 if [[ "$f" != "." ]]; then 755 echo "/$f" 756 fi 757 done 758 } 759 760 mkdir image 761 touch baseFiles 762 baseEnvs='[]' 763 if [[ -n "$fromImage" ]]; then 764 echo "Unpacking base image..." 765 tar -C image -xpf "$fromImage" 766 767 # Store the layers and the environment variables from the base image 768 cat ./image/manifest.json | jq -r '.[0].Layers | .[]' > layer-list 769 configName="$(cat ./image/manifest.json | jq -r '.[0].Config')" 770 baseEnvs="$(cat "./image/$configName" | jq '.config.Env // []')" 771 772 # Extract the parentID from the manifest 773 if [[ -n "$fromImageName" ]] && [[ -n "$fromImageTag" ]]; then 774 parentID="$( 775 cat "image/manifest.json" | 776 jq -r '.[] | select(.RepoTags | contains([$desiredTag])) | rtrimstr(".json")' \ 777 --arg desiredTag "$fromImageName:$fromImageTag" 778 )" 779 else 780 echo "From-image name or tag wasn't set. Reading the first ID." 781 parentID="$(cat "image/manifest.json" | jq -r '.[0].Config | rtrimstr(".json")')" 782 fi 783 784 # Otherwise do not import the base image configuration and manifest 785 chmod a+w image image/*.json 786 rm -f image/*.json 787 788 for l in image/*/layer.tar; do 789 ls_tar $l >> baseFiles 790 done 791 else 792 touch layer-list 793 fi 794 795 chmod -R ug+rw image 796 797 mkdir temp 798 cp ${layer}/* temp/ 799 chmod ug+w temp/* 800 801 for dep in $(cat $layerClosure); do 802 find $dep >> layerFiles 803 done 804 805 echo "Adding layer..." 806 # Record the contents of the tarball with ls_tar. 807 ls_tar temp/layer.tar >> baseFiles 808 809 # Append nix/store directory to the layer so that when the layer is loaded in the 810 # image /nix/store has read permissions for non-root users. 811 # nix/store is added only if the layer has /nix/store paths in it. 812 if [ $(wc -l < $layerClosure) -gt 1 ] && [ $(grep -c -e "^/nix/store$" baseFiles) -eq 0 ]; then 813 mkdir -p nix/store 814 chmod -R 555 nix 815 echo "./nix" >> layerFiles 816 echo "./nix/store" >> layerFiles 817 fi 818 819 # Get the files in the new layer which were *not* present in 820 # the old layer, and record them as newFiles. 821 comm <(sort -n baseFiles|uniq) \ 822 <(sort -n layerFiles|uniq|grep -v ${layer}) -1 -3 > newFiles 823 # Append the new files to the layer. 824 tar -rpf temp/layer.tar --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" \ 825 --owner=0 --group=0 --no-recursion --verbatim-files-from --files-from newFiles 826 827 echo "Adding meta..." 828 829 # If we have a parentID, add it to the json metadata. 830 if [[ -n "$parentID" ]]; then 831 cat temp/json | jshon -s "$parentID" -i parent > tmpjson 832 mv tmpjson temp/json 833 fi 834 835 # Take the sha256 sum of the generated json and use it as the layer ID. 836 # Compute the size and add it to the json under the 'Size' field. 837 layerID=$(sha256sum temp/json|cut -d ' ' -f 1) 838 size=$(stat --printf="%s" temp/layer.tar) 839 cat temp/json | jshon -s "$layerID" -i id -n $size -i Size > tmpjson 840 mv tmpjson temp/json 841 842 # Use the temp folder we've been working on to create a new image. 843 mv temp image/$layerID 844 845 # Add the new layer ID to the end of the layer list 846 ( 847 cat layer-list 848 # originally this used `sed -i "1i$layerID" layer-list`, but 849 # would fail if layer-list was completely empty. 850 echo "$layerID/layer.tar" 851 ) | sponge layer-list 852 853 # Create image json and image manifest 854 imageJson=$(cat ${baseJson} | jq '.config.Env = $baseenv + .config.Env' --argjson baseenv "$baseEnvs") 855 imageJson=$(echo "$imageJson" | jq ". + {\"rootfs\": {\"diff_ids\": [], \"type\": \"layers\"}}") 856 manifestJson=$(jq -n "[{\"RepoTags\":[\"$imageName:$imageTag\"]}]") 857 858 for layerTar in $(cat ./layer-list); do 859 layerChecksum=$(sha256sum image/$layerTar | cut -d ' ' -f1) 860 imageJson=$(echo "$imageJson" | jq ".history |= . + [{\"created\": \"$(jq -r .created ${baseJson})\"}]") 861 # diff_ids order is from the bottom-most to top-most layer 862 imageJson=$(echo "$imageJson" | jq ".rootfs.diff_ids |= . + [\"sha256:$layerChecksum\"]") 863 manifestJson=$(echo "$manifestJson" | jq ".[0].Layers |= . + [\"$layerTar\"]") 864 done 865 866 imageJsonChecksum=$(echo "$imageJson" | sha256sum | cut -d ' ' -f1) 867 echo "$imageJson" > "image/$imageJsonChecksum.json" 868 manifestJson=$(echo "$manifestJson" | jq ".[0].Config = \"$imageJsonChecksum.json\"") 869 echo "$manifestJson" > image/manifest.json 870 871 # Store the json under the name image/repositories. 872 jshon -n object \ 873 -n object -s "$layerID" -i "$imageTag" \ 874 -i "$imageName" > image/repositories 875 876 # Make the image read-only. 877 chmod -R a-w image 878 879 echo "Cooking the image..." 880 tar -C image --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --xform s:'^./':: -c . | ${compress.compress} > $out 881 882 echo "Finished." 883 ''; 884 885 in 886 checked result 887 ); 888 889 # Merge the tarballs of images built with buildImage into a single 890 # tarball that contains all images. Running `docker load` on the resulting 891 # tarball will load the images into the docker daemon. 892 mergeImages = 893 images: 894 runCommand "merge-docker-images" 895 { 896 inherit images; 897 nativeBuildInputs = [ 898 file 899 jq 900 ] 901 ++ compressors.none.nativeInputs 902 ++ compressors.gz.nativeInputs 903 ++ compressors.zstd.nativeInputs; 904 } 905 '' 906 mkdir image inputs 907 # Extract images 908 repos=() 909 manifests=() 910 last_image_mime="application/gzip" 911 for item in $images; do 912 name=$(basename $item) 913 mkdir inputs/$name 914 915 last_image_mime=$(file --mime-type -b $item) 916 case $last_image_mime in 917 "application/x-tar") ${compressors.none.decompress};; 918 "application/zstd") ${compressors.zstd.decompress};; 919 "application/gzip") ${compressors.gz.decompress};; 920 *) echo "error: unexpected layer type $last_image_mime" >&2; exit 1;; 921 esac < $item | tar -xC inputs/$name 922 923 if [ -f inputs/$name/repositories ]; then 924 repos+=(inputs/$name/repositories) 925 fi 926 if [ -f inputs/$name/manifest.json ]; then 927 manifests+=(inputs/$name/manifest.json) 928 fi 929 done 930 # Copy all layers from input images to output image directory 931 cp -R --update=none inputs/*/* image/ 932 # Merge repositories objects and manifests 933 jq -s add "''${repos[@]}" > repositories 934 jq -s add "''${manifests[@]}" > manifest.json 935 # Replace output image repositories and manifest with merged versions 936 mv repositories image/repositories 937 mv manifest.json image/manifest.json 938 # Create tarball and gzip 939 tar -C image --hard-dereference --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --xform s:'^./':: -c . | ( 940 case $last_image_mime in 941 "application/x-tar") ${compressors.none.compress};; 942 "application/zstd") ${compressors.zstd.compress};; 943 "application/gzip") ${compressors.gz.compress};; 944 # `*)` not needed; already checked. 945 esac 946 ) > $out 947 ''; 948 949 # Provide a /etc/passwd and /etc/group that contain root and nobody. 950 # Useful when packaging binaries that insist on using nss to look up 951 # username/groups (like nginx). 952 # /bin/sh is fine to not exist, and provided by another shim. 953 inherit fakeNss; # alias 954 955 # This provides a /usr/bin/env, for shell scripts using the 956 # "#!/usr/bin/env executable" shebang. 957 usrBinEnv = runCommand "usr-bin-env" { } '' 958 mkdir -p $out/usr/bin 959 ln -s ${coreutils}/bin/env $out/usr/bin 960 ''; 961 962 # This provides /bin/sh, pointing to bashInteractive. 963 # The use of bashInteractive here is intentional to support cases like `docker run -it <image_name>`, so keep these use cases in mind if making any changes to how this works. 964 binSh = runCommand "bin-sh" { } '' 965 mkdir -p $out/bin 966 ln -s ${bashInteractive}/bin/bash $out/bin/sh 967 ''; 968 969 # This provides the ca bundle in common locations 970 caCertificates = runCommand "ca-certificates" { } '' 971 mkdir -p $out/etc/ssl/certs $out/etc/pki/tls/certs 972 # Old NixOS compatibility. 973 ln -s ${cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-bundle.crt 974 # NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility. 975 ln -s ${cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs/ca-certificates.crt 976 # CentOS/Fedora compatibility. 977 ln -s ${cacert}/etc/ssl/certs/ca-bundle.crt $out/etc/pki/tls/certs/ca-bundle.crt 978 ''; 979 980 # Build an image and populate its nix database with the provided 981 # contents. The main purpose is to be able to use nix commands in 982 # the container. 983 # Be careful since this doesn't work well with multilayer. 984 # TODO: add the dependencies of the config json. 985 buildImageWithNixDb = args: buildImage (args // { includeNixDB = true; }); 986 987 buildLayeredImageWithNixDb = args: buildLayeredImage (args // { includeNixDB = true; }); 988 989 # Arguments are documented in ../../../doc/build-helpers/images/dockertools.section.md 990 streamLayeredImage = lib.makeOverridable ( 991 { 992 name, 993 tag ? null, 994 fromImage ? null, 995 contents ? [ ], 996 config ? { }, 997 architecture ? defaultArchitecture, 998 created ? "1970-01-01T00:00:01Z", 999 mtime ? "1970-01-01T00:00:01Z", 1000 uid ? 0, 1001 gid ? 0, 1002 uname ? "root", 1003 gname ? "root", 1004 maxLayers ? 100, 1005 extraCommands ? "", 1006 fakeRootCommands ? "", 1007 enableFakechroot ? false, 1008 includeStorePaths ? true, 1009 includeNixDB ? false, 1010 passthru ? { }, 1011 # Pipeline used to produce docker layers. If not set, popularity contest 1012 # algorithm is used. If set, maxLayers is ignored as the author of the 1013 # pipeline can use one of the available functions (like "limit_layers") 1014 # to control the amount of layers. 1015 # See: pkgs/build-support/flatten-references-graph/src/flatten_references_graph/pipe.py 1016 # for available functions, and it's test for how to use them. 1017 # WARNING!! this interface is highly experimental and subject to change. 1018 layeringPipeline ? null, 1019 # Enables debug logging for the layering pipeline. 1020 debug ? false, 1021 }: 1022 assert ( 1023 lib.assertMsg (layeringPipeline == null -> maxLayers > 1) 1024 "the maxLayers argument of dockerTools.buildLayeredImage function must be greather than 1 (current value: ${toString maxLayers})" 1025 ); 1026 assert ( 1027 lib.assertMsg (enableFakechroot -> !stdenv.hostPlatform.isDarwin) '' 1028 cannot use `enableFakechroot` because `proot` is not portable to Darwin. Workarounds: 1029 - use `fakeRootCommands` with the restricted `fakeroot` environment 1030 - cross-compile your packages 1031 - run your packages in a virtual machine 1032 Discussion: https://github.com/NixOS/nixpkgs/issues/327311'' 1033 ); 1034 let 1035 baseName = baseNameOf name; 1036 1037 streamScript = writePython3 "stream" { } ./stream_layered_image.py; 1038 baseJson = writeText "${baseName}-base.json" ( 1039 builtins.toJSON { 1040 inherit config architecture; 1041 os = "linux"; 1042 } 1043 ); 1044 1045 contentsList = if builtins.isList contents then contents else [ contents ]; 1046 bind-paths = builtins.toString ( 1047 builtins.map (path: "--bind=${path}:${path}!") [ 1048 "/dev/" 1049 "/proc/" 1050 "/sys/" 1051 "${builtins.storeDir}/" 1052 "$out/layer.tar" 1053 ] 1054 ); 1055 1056 # We store the customisation layer as a tarball, to make sure that 1057 # things like permissions set on 'extraCommands' are not overridden 1058 # by Nix. Then we precompute the sha256 for performance. 1059 customisationLayer = symlinkJoin { 1060 name = "${baseName}-customisation-layer"; 1061 paths = contentsList; 1062 extraCommands = (lib.optionalString includeNixDB (mkDbExtraCommand contents)) + extraCommands; 1063 inherit fakeRootCommands; 1064 nativeBuildInputs = [ 1065 fakeroot 1066 ] 1067 ++ optionals enableFakechroot [ 1068 proot 1069 ]; 1070 postBuild = '' 1071 mv $out old_out 1072 (cd old_out; eval "$extraCommands" ) 1073 1074 mkdir $out 1075 ${ 1076 if enableFakechroot then 1077 '' 1078 proot -r $PWD/old_out ${bind-paths} --pwd=/ fakeroot bash -e -c ' 1079 if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; fi 1080 source $stdenv/setup 1081 eval "$fakeRootCommands" 1082 tar \ 1083 --sort name \ 1084 --exclude=./dev \ 1085 --exclude=./proc \ 1086 --exclude=./sys \ 1087 --exclude=.${builtins.storeDir} \ 1088 --numeric-owner --mtime "@$SOURCE_DATE_EPOCH" \ 1089 --hard-dereference \ 1090 -cf $out/layer.tar . 1091 ' 1092 '' 1093 else 1094 '' 1095 fakeroot bash -e -c ' 1096 if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; fi 1097 source $stdenv/setup 1098 cd old_out 1099 eval "$fakeRootCommands" 1100 tar \ 1101 --sort name \ 1102 --numeric-owner --mtime "@$SOURCE_DATE_EPOCH" \ 1103 --hard-dereference \ 1104 -cf $out/layer.tar . 1105 ' 1106 '' 1107 } 1108 sha256sum $out/layer.tar \ 1109 | cut -f 1 -d ' ' \ 1110 > $out/checksum 1111 ''; 1112 }; 1113 1114 layersJsonFile = buildPackages.dockerMakeLayers { 1115 inherit debug; 1116 closureRoots = optionals includeStorePaths [ 1117 baseJson 1118 customisationLayer 1119 ]; 1120 excludePaths = [ 1121 baseJson 1122 customisationLayer 1123 ]; 1124 pipeline = 1125 if layeringPipeline != null then 1126 layeringPipeline 1127 else 1128 import ./popularity-contest-layering-pipeline.nix { inherit lib jq runCommand; } { 1129 inherit fromImage maxLayers; 1130 }; 1131 }; 1132 1133 conf = 1134 runCommand "${baseName}-conf.json" 1135 { 1136 inherit 1137 fromImage 1138 created 1139 mtime 1140 uid 1141 gid 1142 uname 1143 gname 1144 layersJsonFile 1145 ; 1146 imageName = lib.toLower name; 1147 preferLocalBuild = true; 1148 passthru.imageTag = 1149 if tag != null then 1150 tag 1151 else 1152 lib.head ( 1153 lib.strings.splitString "-" (baseNameOf (builtins.unsafeDiscardStringContext conf.outPath)) 1154 ); 1155 nativeBuildInputs = [ jq ]; 1156 } 1157 '' 1158 ${ 1159 if (tag == null) then 1160 '' 1161 outName="$(basename "$out")" 1162 outHash=$(echo "$outName" | cut -d - -f 1) 1163 1164 imageTag=$outHash 1165 '' 1166 else 1167 '' 1168 imageTag="${tag}" 1169 '' 1170 } 1171 1172 # convert "created" and "mtime" to iso format 1173 if [[ "$created" != "now" ]]; then 1174 created="$(date -Iseconds -d "$created")" 1175 fi 1176 if [[ "$mtime" != "now" ]]; then 1177 mtime="$(date -Iseconds -d "$mtime")" 1178 fi 1179 1180 jq ' 1181 . + { 1182 "store_dir": $store_dir, 1183 "from_image": $from_image, 1184 "store_layers": $store_layers[0], 1185 "customisation_layer", $customisation_layer, 1186 "repo_tag": $repo_tag, 1187 "created": $created, 1188 "mtime": $mtime, 1189 "uid": $uid, 1190 "gid": $gid, 1191 "uname": $uname, 1192 "gname": $gname 1193 } 1194 ' --arg store_dir "${storeDir}" \ 1195 --argjson from_image ${if fromImage == null then "null" else "'\"${fromImage}\"'"} \ 1196 --slurpfile store_layers "$layersJsonFile" \ 1197 --arg customisation_layer ${customisationLayer} \ 1198 --arg repo_tag "$imageName:$imageTag" \ 1199 --arg created "$created" \ 1200 --arg mtime "$mtime" \ 1201 --arg uid "$uid" \ 1202 --arg gid "$gid" \ 1203 --arg uname "$uname" \ 1204 --arg gname "$gname" \ 1205 ${baseJson} \ 1206 | tee $out 1207 ''; 1208 1209 result = 1210 runCommand "stream-${baseName}" 1211 { 1212 inherit conf; 1213 inherit (conf) imageName; 1214 inherit streamScript; 1215 preferLocalBuild = true; 1216 passthru = passthru // { 1217 inherit (conf) imageTag; 1218 inherit conf; 1219 inherit streamScript; 1220 1221 # Distinguish tarballs and exes at the Nix level so functions that 1222 # take images can know in advance how the image is supposed to be used. 1223 isExe = true; 1224 }; 1225 nativeBuildInputs = [ makeWrapper ]; 1226 } 1227 '' 1228 makeWrapper $streamScript $out --add-flags $conf 1229 ''; 1230 in 1231 result 1232 ); 1233 1234 # This function streams a docker image that behaves like a nix-shell for a derivation 1235 # Docs: doc/build-helpers/images/dockertools.section.md 1236 # Tests: nixos/tests/docker-tools-nix-shell.nix 1237 streamNixShellImage = 1238 { 1239 drv, 1240 name ? drv.name + "-env", 1241 tag ? null, 1242 uid ? 1000, 1243 gid ? 1000, 1244 homeDirectory ? "/build", 1245 shell ? bashInteractive + "/bin/bash", 1246 command ? null, 1247 run ? null, 1248 }: 1249 assert lib.assertMsg (!(drv.drvAttrs.__structuredAttrs or false)) 1250 "streamNixShellImage: Does not work with the derivation ${drv.name} because it uses __structuredAttrs"; 1251 assert lib.assertMsg ( 1252 command == null || run == null 1253 ) "streamNixShellImage: Can't specify both command and run"; 1254 let 1255 1256 # A binary that calls the command to build the derivation 1257 builder = writeShellScriptBin "buildDerivation" '' 1258 exec ${lib.escapeShellArg (valueToString drv.drvAttrs.builder)} ${lib.escapeShellArgs (map valueToString drv.drvAttrs.args)} 1259 ''; 1260 1261 staticPath = "${dirOf shell}:${lib.makeBinPath [ builder ]}"; 1262 1263 # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L493-L526 1264 rcfile = writeText "nix-shell-rc" '' 1265 unset PATH 1266 dontAddDisableDepTrack=1 1267 # TODO: https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L506 1268 [ -e $stdenv/setup ] && source $stdenv/setup 1269 PATH=${staticPath}:"$PATH" 1270 SHELL=${lib.escapeShellArg shell} 1271 BASH=${lib.escapeShellArg shell} 1272 set +e 1273 [ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] ' 1274 if [ "$(type -t runHook)" = function ]; then 1275 runHook shellHook 1276 fi 1277 unset NIX_ENFORCE_PURITY 1278 shopt -u nullglob 1279 shopt -s execfail 1280 ${optionalString (command != null || run != null) '' 1281 ${optionalString (command != null) command} 1282 ${optionalString (run != null) run} 1283 exit 1284 ''} 1285 ''; 1286 1287 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/globals.hh#L464-L465 1288 sandboxBuildDir = "/build"; 1289 1290 drvEnv = 1291 devShellTools.unstructuredDerivationInputEnv { inherit (drv) drvAttrs; } 1292 // devShellTools.derivationOutputEnv { 1293 outputList = drv.outputs; 1294 outputMap = drv; 1295 }; 1296 1297 # Environment variables set in the image 1298 envVars = { 1299 1300 # Root certificates for internet access 1301 SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; 1302 NIX_SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; 1303 1304 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1027-L1030 1305 # PATH = "/path-not-set"; 1306 # Allows calling bash and `buildDerivation` as the Cmd 1307 PATH = staticPath; 1308 1309 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1032-L1038 1310 HOME = homeDirectory; 1311 1312 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1040-L1044 1313 NIX_STORE = storeDir; 1314 1315 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1046-L1047 1316 # TODO: Make configurable? 1317 NIX_BUILD_CORES = "1"; 1318 1319 } 1320 // drvEnv 1321 // { 1322 1323 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1008-L1010 1324 NIX_BUILD_TOP = sandboxBuildDir; 1325 1326 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1012-L1013 1327 TMPDIR = sandboxBuildDir; 1328 TEMPDIR = sandboxBuildDir; 1329 TMP = sandboxBuildDir; 1330 TEMP = sandboxBuildDir; 1331 1332 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1015-L1019 1333 PWD = sandboxBuildDir; 1334 1335 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1071-L1074 1336 # We don't set it here because the output here isn't handled in any special way 1337 # NIX_LOG_FD = "2"; 1338 1339 # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1076-L1077 1340 TERM = "xterm-256color"; 1341 }; 1342 1343 in 1344 streamLayeredImage { 1345 inherit name tag; 1346 contents = [ 1347 binSh 1348 usrBinEnv 1349 (fakeNss.override { 1350 # Allows programs to look up the build user's home directory 1351 # https://github.com/NixOS/nix/blob/ffe155abd36366a870482625543f9bf924a58281/src/libstore/build/local-derivation-goal.cc#L906-L910 1352 # Slightly differs however: We use the passed-in homeDirectory instead of sandboxBuildDir. 1353 # We're doing this because it's arguably a bug in Nix that sandboxBuildDir is used here: https://github.com/NixOS/nix/issues/6379 1354 extraPasswdLines = [ 1355 "nixbld:x:${toString uid}:${toString gid}:Build user:${homeDirectory}:/noshell" 1356 ]; 1357 extraGroupLines = [ 1358 "nixbld:!:${toString gid}:" 1359 ]; 1360 }) 1361 ]; 1362 1363 fakeRootCommands = '' 1364 # Effectively a single-user installation of Nix, giving the user full 1365 # control over the Nix store. Needed for building the derivation this 1366 # shell is for, but also in case one wants to use Nix inside the 1367 # image 1368 mkdir -p ./nix/{store,var/nix} ./etc/nix 1369 chown -R ${toString uid}:${toString gid} ./nix ./etc/nix 1370 1371 # Gives the user control over the build directory 1372 mkdir -p .${sandboxBuildDir} 1373 chown -R ${toString uid}:${toString gid} .${sandboxBuildDir} 1374 ''; 1375 1376 # Run this image as the given uid/gid 1377 config.User = "${toString uid}:${toString gid}"; 1378 config.Cmd = 1379 # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L185-L186 1380 # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L534-L536 1381 if run == null then 1382 [ 1383 shell 1384 "--rcfile" 1385 rcfile 1386 ] 1387 else 1388 [ 1389 shell 1390 rcfile 1391 ]; 1392 config.WorkingDir = sandboxBuildDir; 1393 config.Env = lib.mapAttrsToList (name: value: "${name}=${value}") envVars; 1394 }; 1395 1396 # Wrapper around streamNixShellImage to build an image from the result 1397 # Docs: doc/build-helpers/images/dockertools.section.md 1398 # Tests: nixos/tests/docker-tools-nix-shell.nix 1399 buildNixShellImage = 1400 { 1401 drv, 1402 compressor ? "gz", 1403 ... 1404 }@args: 1405 let 1406 stream = streamNixShellImage (builtins.removeAttrs args [ "compressor" ]); 1407 compress = compressorForImage compressor drv.name; 1408 in 1409 runCommand "${drv.name}-env.tar${compress.ext}" { 1410 inherit (stream) imageName; 1411 passthru = { inherit (stream) imageTag; }; 1412 nativeBuildInputs = compress.nativeInputs; 1413 } "${stream} | ${compress.compress} > $out"; 1414}