2021-08-10 14:31:46 +00:00
{ bashInteractive
, buildPackages
, cacert
, callPackage
, closureInfo
, coreutils
2024-07-27 06:49:29 +00:00
, devShellTools
2021-08-10 14:31:46 +00:00
, e2fsprogs
2023-08-22 20:05:09 +00:00
, proot
2022-04-03 18:54:34 +00:00
, fakeNss
2021-08-10 14:31:46 +00:00
, fakeroot
2024-02-29 20:09:43 +00:00
, file
2021-08-10 14:31:46 +00:00
, go
, jq
, jshon
, lib
, makeWrapper
, moreutils
, nix
2022-04-15 01:41:22 +00:00
, nixosTests
2021-08-10 14:31:46 +00:00
, pigz
, rsync
, runCommand
, runtimeShell
, shadow
, skopeo
2024-07-27 06:49:29 +00:00
, stdenv
2021-08-10 14:31:46 +00:00
, storeDir ? builtins . storeDir
, substituteAll
, symlinkJoin
2022-04-15 01:41:22 +00:00
, tarsum
2021-08-10 14:31:46 +00:00
, util-linux
, vmTools
2024-04-21 15:54:59 +00:00
, writeClosure
2021-08-10 14:31:46 +00:00
, writeScript
2022-11-27 09:42:12 +00:00
, writeShellScriptBin
2021-08-10 14:31:46 +00:00
, writeText
, writeTextDir
, writePython3
2024-02-29 20:09:43 +00:00
, zstd
2020-04-24 23:36:52 +00:00
} :
2020-05-03 17:38:23 +00:00
let
2021-12-06 16:07:01 +00:00
inherit ( lib )
optionals
optionalString
;
2020-05-03 17:38:23 +00:00
2021-10-17 09:34:42 +00:00
inherit ( lib )
escapeShellArgs
toList
;
2024-07-27 06:49:29 +00:00
inherit ( devShellTools )
valueToString
;
2021-08-10 14:31:46 +00:00
mkDbExtraCommand = contents :
let
contentsList = if builtins . isList contents then contents else [ contents ] ;
in
''
echo " G e n e r a t i n g t h e n i x d a t a b a s e . . . "
echo " W a r n i n g : o n l y t h e d a t a b a s e o f t h e d e e p e s t N i x l a y e r i s l o a d e d . "
echo " I f y o u w a n t t o u s e n i x c o m m a n d s i n t h e c o n t a i n e r , i t w o u l d "
echo " b e b e t t e r t o o n l y h a v e o n e l a y e r t h a t c o n t a i n s a n i x s t o r e . "
export NIX_REMOTE = local ? root = $ PWD
# A user is required by nix
# https://github.com/NixOS/nix/blob/9348f9291e5d9e4ba3c4347ea1b235640f54fd79/src/libutil/util.cc#L478
export USER = nobody
$ { buildPackages . nix } /bin/nix-store - - load-db < $ { closureInfo { rootPaths = contentsList ; } } /registration
2024-02-29 20:09:43 +00:00
# Reset registration times to make the image reproducible
$ { buildPackages . sqlite } /bin/sqlite3 nix/var/nix/db/db.sqlite " U P D A T E V a l i d P a t h s S E T r e g i s t r a t i o n T i m e = ' ' ${ SOURCE_DATE_EPOCH } "
2021-08-10 14:31:46 +00:00
mkdir - p nix/var/nix/gcroots/docker /
for i in $ { lib . concatStringsSep " " contentsList } ; do
ln - s $ i nix/var/nix/gcroots/docker / $ ( basename $ i )
done ;
'' ;
2020-04-24 23:36:52 +00:00
2020-12-25 13:55:36 +00:00
# The OCI Image specification recommends that configurations use values listed
# in the Go Language document for GOARCH.
# Reference: https://github.com/opencontainers/image-spec/blob/master/config.md#properties
# For the mapping from Nixpkgs system parameters to GOARCH, we can reuse the
# mapping from the go package.
2022-12-17 10:02:37 +00:00
defaultArchitecture = go . GOARCH ;
2020-11-21 19:51:51 +00:00
2024-02-29 20:09:43 +00:00
compressors = {
none = {
ext = " " ;
nativeInputs = [ ] ;
compress = " c a t " ;
decompress = " c a t " ;
} ;
gz = {
ext = " . g z " ;
nativeInputs = [ pigz ] ;
compress = " p i g z - p $ N I X _ B U I L D _ C O R E S - n T R " ;
decompress = " p i g z - d - p $ N I X _ B U I L D _ C O R E S " ;
} ;
zstd = {
ext = " . z s t " ;
nativeInputs = [ zstd ] ;
compress = " z s t d - T $ N I X _ B U I L D _ C O R E S " ;
decompress = " z s t d - d - T $ N I X _ B U I L D _ C O R E S " ;
} ;
} ;
compressorForImage = compressor : imageName : compressors . ${ compressor } or
( throw " i n d o c k e r i m a g e ${ imageName } : c o m p r e s s o r m u s t b e o n e o f : [ ${ toString builtins . attrNames compressors } ] " ) ;
2020-05-03 17:38:23 +00:00
in
2020-04-24 23:36:52 +00:00
rec {
examples = callPackage ./examples.nix {
2022-11-27 09:42:12 +00:00
inherit buildImage buildLayeredImage fakeNss pullImage shadowSetup buildImageWithNixDb streamNixShellImage ;
2020-04-24 23:36:52 +00:00
} ;
2022-04-15 01:41:22 +00:00
tests = {
inherit ( nixosTests )
docker-tools
docker-tools-overlay
# requires remote builder
# docker-tools-cross
;
} ;
2021-08-10 14:31:46 +00:00
pullImage =
let
fixName = name : builtins . replaceStrings [ " / " " : " ] [ " - " " - " ] name ;
in
2020-04-24 23:36:52 +00:00
{ imageName
# To find the digest of an image, you can use skopeo:
# see doc/functions.xml
, imageDigest
, sha256
, os ? " l i n u x "
2022-12-17 10:02:37 +00:00
, # Image architecture, defaults to the architecture of the `hostPlatform` when unset
arch ? defaultArchitecture
2020-04-24 23:36:52 +00:00
# This is used to set name to the pulled image
, finalImageName ? imageName
# This used to set a tag to the pulled image
, finalImageTag ? " l a t e s t "
2021-06-04 09:07:49 +00:00
# This is used to disable TLS certificate verification, allowing access to http registries on (hopefully) trusted networks
, tlsVerify ? true
2020-04-24 23:36:52 +00:00
, name ? fixName " d o c k e r - i m a g e - ${ finalImageName } - ${ finalImageTag } . t a r "
} :
2021-08-10 14:31:46 +00:00
runCommand name
{
inherit imageDigest ;
imageName = finalImageName ;
imageTag = finalImageTag ;
impureEnvVars = lib . fetchers . proxyImpureEnvVars ;
outputHashMode = " f l a t " ;
outputHashAlgo = " s h a 2 5 6 " ;
outputHash = sha256 ;
2022-04-15 01:41:22 +00:00
nativeBuildInputs = [ skopeo ] ;
2021-08-10 14:31:46 +00:00
SSL_CERT_FILE = " ${ cacert . out } / e t c / s s l / c e r t s / c a - b u n d l e . c r t " ;
sourceURL = " d o c k e r : / / ${ imageName } @ ${ imageDigest } " ;
destNameTag = " ${ finalImageName } : ${ finalImageTag } " ;
} ''
2021-06-04 09:07:49 +00:00
skopeo \
- - insecure-policy \
- - tmpdir = $ TMPDIR \
- - override-os $ { os } \
- - override-arch $ { arch } \
2021-08-14 21:54:09 +00:00
copy \
- - src-tls-verify = $ { lib . boolToString tlsVerify } \
" $ s o u r c e U R L " " d o c k e r - a r c h i v e : / / $ o u t : $ d e s t N a m e T a g " \
2021-07-04 02:40:35 +00:00
| cat # pipe through cat to force-disable progress bar
2020-04-24 23:36:52 +00:00
'' ;
# We need to sum layer.tar, not a directory, hence tarsum instead of nix-hash.
2021-01-15 22:18:51 +00:00
# And we cannot untar it, because then we cannot preserve permissions etc.
2022-04-15 01:41:22 +00:00
inherit tarsum ; # pkgs.dockerTools.tarsum
2020-04-24 23:36:52 +00:00
# buildEnv creates symlinks to dirs, which is hard to edit inside the overlay VM
2021-08-10 14:31:46 +00:00
mergeDrvs =
{ derivations
, onlyDeps ? false
} :
runCommand " m e r g e - d r v s "
{
inherit derivations onlyDeps ;
} ''
2020-04-24 23:36:52 +00:00
if [ [ - n " $ o n l y D e p s " ] ] ; then
echo $ derivations > $ out
exit 0
fi
mkdir $ out
for derivation in $ derivations ; do
echo " M e r g i n g $ d e r i v a t i o n . . . "
if [ [ - d " $ d e r i v a t i o n " ] ] ; then
# If it's a directory, copy all of its contents into $out.
cp - drf - - preserve = mode - f $ derivation /* $ o u t /
else
# Otherwise treat the derivation as a tarball and extract it
# into $out.
tar - C $ out - xpf $ drv || true
fi
done
'' ;
# Helper for setting up the base files for managing users and
# groups, only if such files don't exist already. It is suitable for
# being used in a runAsRoot script.
shadowSetup = ''
export PATH = $ { shadow } /bin : $ PATH
mkdir - p /etc/pam.d
if [ [ ! - f /etc/passwd ] ] ; then
echo " r o o t : x : 0 : 0 : : / r o o t : ${ runtimeShell } " > /etc/passwd
echo " r o o t : ! x : : : : : : : " > /etc/shadow
fi
if [ [ ! - f /etc/group ] ] ; then
echo " r o o t : x : 0 : " > /etc/group
echo " r o o t : x : : " > /etc/gshadow
fi
if [ [ ! - f /etc/pam.d/other ] ] ; then
cat > /etc/pam.d/other < < EOF
account sufficient pam_unix . so
auth sufficient pam_rootok . so
2023-03-27 19:17:25 +00:00
password requisite pam_unix . so nullok yescrypt
2020-04-24 23:36:52 +00:00
session required pam_unix . so
EOF
fi
if [ [ ! - f /etc/login.defs ] ] ; then
touch /etc/login.defs
fi
'' ;
# Run commands in a virtual machine.
2021-08-10 14:31:46 +00:00
runWithOverlay =
{ name
, fromImage ? null
, fromImageName ? null
, fromImageTag ? null
, diskSize ? 1024
2022-08-21 13:32:41 +00:00
, buildVMMemorySize ? 512
2021-08-10 14:31:46 +00:00
, preMount ? " "
, postMount ? " "
, postUmount ? " "
} :
2021-10-01 09:20:50 +00:00
vmTools . runInLinuxVM (
2021-08-10 14:31:46 +00:00
runCommand name
{
preVM = vmTools . createEmptyImage {
size = diskSize ;
fullName = " d o c k e r - r u n - d i s k " ;
2021-10-01 09:20:50 +00:00
destination = " . / i m a g e " ;
2021-08-10 14:31:46 +00:00
} ;
inherit fromImage fromImageName fromImageTag ;
2022-08-21 13:32:41 +00:00
memSize = buildVMMemorySize ;
2021-08-10 14:31:46 +00:00
nativeBuildInputs = [ util-linux e2fsprogs jshon rsync jq ] ;
} ''
mkdir disk
mkfs /dev / $ { vmTools . hd }
mount /dev / $ { vmTools . hd } disk
cd disk
2023-03-15 16:39:30 +00:00
function dedup ( ) {
declare - A seen
while read ln ; do
if [ [ - z " ' ' ${ seen [ " $ l n " ] : - } " ] ] ; then
echo " $ l n " ; seen [ " $ l n " ] = 1
fi
done
}
2021-08-10 14:31:46 +00:00
if [ [ - n " $ f r o m I m a g e " ] ] ; then
echo " U n p a c k i n g b a s e i m a g e . . . "
mkdir image
tar - C image - xpf " $ f r o m I m a g e "
if [ [ - n " $ f r o m I m a g e N a m e " ] ] && [ [ - n " $ f r o m I m a g e T a g " ] ] ; then
parentID = " $ (
cat " i m a g e / m a n i f e s t . j s o n " |
jq - r ' . [ ] | select ( . RepoTags | contains ( [ $ desiredTag ] ) ) | rtrimstr ( " . j s o n " ) ' \
- - arg desiredTag " $ f r o m I m a g e N a m e : $ f r o m I m a g e T a g "
) "
else
echo " F r o m - i m a g e n a m e o r t a g w a s n ' t s e t . R e a d i n g t h e f i r s t I D . "
parentID = " $ ( c a t " image/manifest.json " | j q - r ' . [ 0 ] . C o n f i g | r t r i m s t r ( " . json " ) ' ) "
fi
2020-04-24 23:36:52 +00:00
2023-03-15 16:39:30 +00:00
# In case of repeated layers, unpack only the last occurrence of each
cat ./image/manifest.json | jq - r ' . [ 0 ] . Layers | . [ ] ' | tac | dedup | tac > layer-list
2021-08-10 14:31:46 +00:00
else
touch layer-list
fi
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
# Unpack all of the parent layers into the image.
lowerdir = " "
extractionID = 0
2021-12-19 01:06:50 +00:00
for layerTar in $ ( cat layer-list ) ; do
2021-08-10 14:31:46 +00:00
echo " U n p a c k i n g l a y e r $ l a y e r T a r "
extractionID = $ ( ( extractionID + 1 ) )
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
mkdir - p image / $ extractionID/layer
tar - C image / $ extractionID/layer - xpf image / $ layerTar
rm image / $ layerTar
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
find image / $ extractionID/layer - name " . w h . * " - exec bash - c ' name = " $ ( b a s e n a m e { } | s e d " s / ^ . wh . // " ) " ; mknod " $ ( d i r n a m e { } ) / $ n a m e " c 0 0 ; rm { } ' \ ;
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
# Get the next lower directory and continue the loop.
lowerdir = image / $ extractionID/layer '' ${ lowerdir:+: } $l o w e r d i r
done
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
mkdir work
mkdir layer
mkdir mnt
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
$ { lib . optionalString ( preMount != " " ) ''
# Execute pre-mount steps
echo " E x e c u t i n g p r e - m o u n t s t e p s . . . "
$ { preMount }
'' }
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
if [ - n " $ l o w e r d i r " ] ; then
mount - t overlay overlay - olowerdir = $ lowerdir , workdir = work , upperdir = layer mnt
else
mount - - bind layer mnt
fi
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
$ { lib . optionalString ( postMount != " " ) ''
# Execute post-mount steps
echo " E x e c u t i n g p o s t - m o u n t s t e p s . . . "
$ { postMount }
'' }
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
umount mnt
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
(
cd layer
cmd = ' name = " $ ( b a s e n a m e { } ) " ; touch " $ ( d i r n a m e { } ) / . w h . $ n a m e " ; rm " { } " '
find . - type c - exec bash - c " $ c m d " \ ;
)
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
$ { postUmount }
'' ) ;
2020-04-24 23:36:52 +00:00
exportImage = { name ? fromImage . name , fromImage , fromImageName ? null , fromImageTag ? null , diskSize ? 1024 }:
runWithOverlay {
inherit name fromImage fromImageName fromImageTag diskSize ;
postMount = ''
echo " P a c k i n g r a w i m a g e . . . "
2021-10-01 09:20:50 +00:00
tar - C mnt - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - cf $ out/layer.tar .
'' ;
postUmount = ''
mv $ out/layer.tar .
rm - rf $ out
mv layer . tar $ out
2020-04-24 23:36:52 +00:00
'' ;
} ;
# Create an executable shell script which has the coreutils in its
# PATH. Since root scripts are executed in a blank environment, even
# things like `ls` or `echo` will be missing.
shellScript = name : text :
writeScript name ''
#!${runtimeShell}
set - e
export PATH = $ { coreutils } /bin : /bin
$ { text }
'' ;
# Create a "layer" (set of files).
2021-08-10 14:31:46 +00:00
mkPureLayer =
{
# Name of the layer
name
, # JSON containing configuration and metadata for this layer.
baseJson
, # Files to add to the layer.
2022-07-14 12:49:19 +00:00
copyToRoot ? null
2021-08-10 14:31:46 +00:00
, # When copying the contents into the image, preserve symlinks to
# directories (see `rsync -K`). Otherwise, transform those symlinks
# into directories.
keepContentsDirlinks ? false
, # Additional commands to run on the layer before it is tar'd up.
extraCommands ? " "
, uid ? 0
, gid ? 0
} :
runCommand " d o c k e r - l a y e r - ${ name } "
{
2022-07-14 12:49:19 +00:00
inherit baseJson extraCommands ;
contents = copyToRoot ;
2021-08-10 14:31:46 +00:00
nativeBuildInputs = [ jshon rsync tarsum ] ;
}
''
mkdir layer
if [ [ - n " $ c o n t e n t s " ] ] ; then
echo " A d d i n g c o n t e n t s . . . "
for item in $ contents ; do
echo " A d d i n g $ i t e m "
rsync - a $ { if keepContentsDirlinks then " K " else " k " } - - chown = 0 : 0 $ item / layer /
done
else
echo " N o c o n t e n t s t o a d d t o l a y e r . "
fi
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
chmod ug + w layer
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
if [ [ - n " $ e x t r a C o m m a n d s " ] ] ; then
( cd layer ; eval " $ e x t r a C o m m a n d s " )
fi
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
# Tar up the layer and throw it into 'layer.tar'.
echo " P a c k i n g l a y e r . . . "
mkdir $ out
tarhash = $ ( tar - C layer - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - - owner = $ { toString uid } - - group = $ { toString gid } - cf - . | tee - p $ out/layer.tar | tarsum )
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
# Add a 'checksum' field to the JSON, with the value set to the
# checksum of the tarball.
cat $ { baseJson } | jshon - s " $ t a r h a s h " - i checksum > $ out/json
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
# Indicate to docker that we're using schema version 1.0.
echo - n " 1 . 0 " > $ out/VERSION
2020-04-24 23:36:52 +00:00
2021-08-10 14:31:46 +00:00
echo " F i n i s h e d b u i l d i n g l a y e r ' ${ name } ' "
'' ;
2020-04-24 23:36:52 +00:00
# Make a "root" layer; required if we need to execute commands as a
# privileged user on the image. The commands themselves will be
# performed in a virtual machine sandbox.
2021-08-10 14:31:46 +00:00
mkRootLayer =
{
# Name of the image.
name
, # Script to run as root. Bash.
runAsRoot
, # Files to add to the layer. If null, an empty layer will be created.
2022-07-14 12:49:19 +00:00
# To add packages to /bin, use `buildEnv` or similar.
copyToRoot ? null
2021-08-10 14:31:46 +00:00
, # When copying the contents into the image, preserve symlinks to
# directories (see `rsync -K`). Otherwise, transform those symlinks
# into directories.
keepContentsDirlinks ? false
, # JSON containing configuration and metadata for this layer.
baseJson
, # Existing image onto which to append the new layer.
fromImage ? null
, # Name of the image we're appending onto.
fromImageName ? null
, # Tag of the image we're appending onto.
fromImageTag ? null
, # How much disk to allocate for the temporary virtual machine.
diskSize ? 1024
2022-08-21 13:32:41 +00:00
, # How much memory to allocate for the temporary virtual machine.
buildVMMemorySize ? 512
2021-08-10 14:31:46 +00:00
, # Commands (bash) to run on the layer; these do not require sudo.
extraCommands ? " "
} :
2020-04-24 23:36:52 +00:00
# Generate an executable script from the `runAsRoot` text.
let
runAsRootScript = shellScript " r u n - a s - r o o t . s h " runAsRoot ;
extraCommandsScript = shellScript " e x t r a - c o m m a n d s . s h " extraCommands ;
2021-08-10 14:31:46 +00:00
in
runWithOverlay {
2020-04-24 23:36:52 +00:00
name = " d o c k e r - l a y e r - ${ name } " ;
2022-08-21 13:32:41 +00:00
inherit fromImage fromImageName fromImageTag diskSize buildVMMemorySize ;
2020-04-24 23:36:52 +00:00
2022-07-14 12:49:19 +00:00
preMount = lib . optionalString ( copyToRoot != null && copyToRoot != [ ] ) ''
2020-04-24 23:36:52 +00:00
echo " A d d i n g c o n t e n t s . . . "
2022-07-14 12:49:19 +00:00
for item in $ { escapeShellArgs ( map ( c : " ${ c } " ) ( toList copyToRoot ) ) } ; do
2020-04-24 23:36:52 +00:00
echo " A d d i n g $ i t e m . . . "
rsync - a $ { if keepContentsDirlinks then " K " else " k " } - - chown = 0 : 0 $ item / layer /
done
chmod ug + w layer
'' ;
postMount = ''
2023-10-19 13:55:26 +00:00
mkdir - p mnt / { dev , proc , sys , tmp } mnt $ { storeDir }
2020-04-24 23:36:52 +00:00
# Mount /dev, /sys and the nix store as shared folders.
mount - - rbind /dev mnt/dev
mount - - rbind /sys mnt/sys
mount - - rbind $ { storeDir } mnt $ { storeDir }
# Execute the run as root script. See 'man unshare' for
# details on what's going on here; basically this command
# means that the runAsRootScript will be executed in a nearly
# completely isolated environment.
2021-04-12 18:23:04 +00:00
#
# Ideally we would use --mount-proc=mnt/proc or similar, but this
# doesn't work. The workaround is to setup proc after unshare.
# See: https://github.com/karelzak/util-linux/issues/648
unshare - imnpuf - - mount-proc sh - c ' mount - - rbind /proc mnt/proc && chroot mnt $ { runAsRootScript } '
2020-04-24 23:36:52 +00:00
# Unmount directories and remove them.
umount - R mnt/dev mnt/sys mnt $ { storeDir }
rmdir - - ignore-fail-on-non-empty \
mnt/dev mnt/proc mnt/sys mnt $ { storeDir } \
mnt $ ( dirname $ { storeDir } )
'' ;
postUmount = ''
( cd layer ; $ { extraCommandsScript } )
echo " P a c k i n g l a y e r . . . "
mkdir - p $ out
2020-05-29 06:06:01 +00:00
tarhash = $ ( tar - C layer - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - cf - . |
2020-11-06 00:33:48 +00:00
tee - p $ out/layer.tar |
2020-05-29 06:06:01 +00:00
$ { tarsum } /bin/tarsum )
2020-04-24 23:36:52 +00:00
cat $ { baseJson } | jshon - s " $ t a r h a s h " - i checksum > $ out/json
# Indicate to docker that we're using schema version 1.0.
echo - n " 1 . 0 " > $ out/VERSION
echo " F i n i s h e d b u i l d i n g l a y e r ' ${ name } ' "
'' ;
} ;
2024-02-29 20:09:43 +00:00
buildLayeredImage = lib . makeOverridable ( { name , compressor ? " g z " , . . . } @ args :
2020-04-24 23:36:52 +00:00
let
2024-04-21 15:54:59 +00:00
stream = streamLayeredImage ( builtins . removeAttrs args [ " c o m p r e s s o r " ] ) ;
2024-02-29 20:09:43 +00:00
compress = compressorForImage compressor name ;
2020-04-24 23:36:52 +00:00
in
2024-02-29 20:09:43 +00:00
runCommand " ${ baseNameOf name } . t a r ${ compress . ext } "
2021-08-10 14:31:46 +00:00
{
2020-07-18 16:06:22 +00:00
inherit ( stream ) imageName ;
2024-04-21 15:54:59 +00:00
passthru = { inherit ( stream ) imageTag ; inherit stream ; } ;
2024-02-29 20:09:43 +00:00
nativeBuildInputs = compress . nativeInputs ;
} " ${ stream } | ${ compress . compress } > $ o u t "
2023-10-09 19:29:22 +00:00
) ;
2020-04-24 23:36:52 +00:00
# 1. extract the base image
# 2. create the layer
# 3. add layer deps to the layer itself, diffing with the base image
# 4. compute the layer id
# 5. put the layer in the image
# 6. repack the image
2023-10-09 19:29:22 +00:00
buildImage = lib . makeOverridable (
2021-08-10 14:31:46 +00:00
args @ {
# Image name.
name
, # Image tag, when null then the nix output hash will be used.
tag ? null
, # Parent image, to append to.
fromImage ? null
, # Name of the parent image; will be read from the image otherwise.
fromImageName ? null
, # Tag of the parent image; will be read from the image otherwise.
fromImageTag ? null
, # Files to put on the image (a nix store path or list of paths).
2022-07-14 12:49:19 +00:00
copyToRoot ? null
2021-08-10 14:31:46 +00:00
, # When copying the contents into the image, preserve symlinks to
# directories (see `rsync -K`). Otherwise, transform those symlinks
# into directories.
keepContentsDirlinks ? false
, # Docker config; e.g. what command to run on the container.
config ? null
2022-12-17 10:02:37 +00:00
, # Image architecture, defaults to the architecture of the `hostPlatform` when unset
architecture ? defaultArchitecture
2021-08-10 14:31:46 +00:00
, # Optional bash script to run on the files prior to fixturizing the layer.
extraCommands ? " "
, uid ? 0
, gid ? 0
, # Optional bash script to run as root on the image when provisioning.
runAsRoot ? null
, # Size of the virtual machine disk to provision when building the image.
diskSize ? 1024
2022-08-21 13:32:41 +00:00
, # Size of the virtual machine memory to provision when building the image.
buildVMMemorySize ? 512
2021-08-10 14:31:46 +00:00
, # Time of creation of the image.
created ? " 1 9 7 0 - 0 1 - 0 1 T 0 0 : 0 0 : 0 1 Z "
2024-02-29 20:09:43 +00:00
, # Compressor to use. One of: none, gz, zstd.
compressor ? " g z "
2024-07-27 06:49:29 +00:00
# Populate the nix database in the image with the dependencies of `copyToRoot`.
, includeNixDB ? false
2022-07-14 12:49:19 +00:00
, # Deprecated.
contents ? null
2021-08-10 14:31:46 +00:00
,
} :
2020-04-24 23:36:52 +00:00
let
2022-07-14 12:49:19 +00:00
checked =
lib . warnIf ( contents != null )
" i n d o c k e r i m a g e ${ name } : T h e c o n t e n t s p a r a m e t e r i s d e p r e c a t e d . C h a n g e t o c o p y T o R o o t i f t h e c o n t e n t s a r e d e s i g n e d t o b e c o p i e d t o t h e r o o t f i l e s y s t e m , s u c h a s w h e n y o u u s e ` b u i l d E n v ` o r s i m i l a r b e t w e e n c o n t e n t s a n d y o u r p a c k a g e s . U s e c o p y T o R o o t = b u i l d E n v { . . . } ; o r s i m i l a r i f y o u i n t e n d t o a d d p a c k a g e s t o / b i n . "
lib . throwIf ( contents != null && copyToRoot != null ) " i n d o c k e r i m a g e ${ name } : Y o u c a n n o t s p e c i f y b o t h c o n t e n t s a n d c o p y T o R o o t . "
;
rootContents = if copyToRoot == null then contents else copyToRoot ;
2020-04-24 23:36:52 +00:00
baseName = baseNameOf name ;
# Create a JSON blob of the configuration. Set the date to unix zero.
2021-08-10 14:31:46 +00:00
baseJson =
let
2020-04-24 23:36:52 +00:00
pure = writeText " ${ baseName } - c o n f i g . j s o n " ( builtins . toJSON {
2022-12-17 10:02:37 +00:00
inherit created config architecture ;
2022-11-27 09:42:12 +00:00
preferLocalBuild = true ;
2020-04-24 23:36:52 +00:00
os = " l i n u x " ;
} ) ;
impure = runCommand " ${ baseName } - c o n f i g . j s o n "
2022-11-27 09:42:12 +00:00
{
nativeBuildInputs = [ jq ] ;
preferLocalBuild = true ;
}
2020-04-24 23:36:52 +00:00
''
2021-08-10 14:31:46 +00:00
jq " . c r e a t e d = \" $ ( T Z = u t c d a t e - - i s o - 8 6 0 1 = " seconds " ) \" " $ { pure } > $ out
2020-04-24 23:36:52 +00:00
'' ;
2021-08-10 14:31:46 +00:00
in
if created == " n o w " then impure else pure ;
2020-04-24 23:36:52 +00:00
2024-02-29 20:09:43 +00:00
compress = compressorForImage compressor name ;
2024-07-27 06:49:29 +00:00
# TODO: add the dependencies of the config json.
extraCommandsWithDB =
if includeNixDB then ( mkDbExtraCommand rootContents ) + extraCommands
else extraCommands ;
2020-04-24 23:36:52 +00:00
layer =
if runAsRoot == null
2021-08-10 14:31:46 +00:00
then
mkPureLayer
{
name = baseName ;
2024-07-27 06:49:29 +00:00
inherit baseJson keepContentsDirlinks uid gid ;
extraCommands = extraCommandsWithDB ;
2022-07-14 12:49:19 +00:00
copyToRoot = rootContents ;
2021-08-10 14:31:46 +00:00
} else
mkRootLayer {
name = baseName ;
inherit baseJson fromImage fromImageName fromImageTag
2024-07-27 06:49:29 +00:00
keepContentsDirlinks runAsRoot diskSize buildVMMemorySize ;
extraCommands = extraCommandsWithDB ;
2022-07-14 12:49:19 +00:00
copyToRoot = rootContents ;
2021-08-10 14:31:46 +00:00
} ;
2024-02-29 20:09:43 +00:00
result = runCommand " d o c k e r - i m a g e - ${ baseName } . t a r ${ compress . ext } "
2021-08-10 14:31:46 +00:00
{
2024-02-29 20:09:43 +00:00
nativeBuildInputs = [ jshon jq moreutils ] ++ compress . nativeInputs ;
2021-08-10 14:31:46 +00:00
# Image name must be lowercase
imageName = lib . toLower name ;
2023-08-04 22:07:22 +00:00
imageTag = lib . optionalString ( tag != null ) tag ;
2021-08-10 14:31:46 +00:00
inherit fromImage baseJson ;
2024-04-21 15:54:59 +00:00
layerClosure = writeClosure [ layer ] ;
2021-08-10 14:31:46 +00:00
passthru . buildArgs = args ;
passthru . layer = layer ;
passthru . imageTag =
if tag != null
2021-06-04 09:07:49 +00:00
then tag
2020-07-18 16:06:22 +00:00
else
2024-04-21 15:54:59 +00:00
lib . head ( lib . strings . splitString " - " ( baseNameOf ( builtins . unsafeDiscardStringContext result . outPath ) ) ) ;
2021-08-10 14:31:46 +00:00
} ''
2020-04-24 23:36:52 +00:00
$ { lib . optionalString ( tag == null ) ''
outName = " $ ( b a s e n a m e " $ out " ) "
outHash = $ ( echo " $ o u t N a m e " | cut - d - - f 1 )
imageTag = $ outHash
'' }
# Print tar contents:
# 1: Interpreted as relative to the root directory
# 2: With no trailing slashes on directories
# This is useful for ensuring that the output matches the
# values generated by the "find" command
ls_tar ( ) {
for f in $ ( tar - tf $ 1 | xargs realpath - ms - - relative-to = . ) ; do
if [ [ " $ f " != " . " ] ] ; then
echo " / $ f "
fi
done
}
mkdir image
touch baseFiles
2020-05-15 21:57:56 +00:00
baseEnvs = ' [ ] '
2020-04-24 23:36:52 +00:00
if [ [ - n " $ f r o m I m a g e " ] ] ; then
echo " U n p a c k i n g b a s e i m a g e . . . "
tar - C image - xpf " $ f r o m I m a g e "
2020-05-15 21:57:56 +00:00
# Store the layers and the environment variables from the base image
2020-04-24 23:36:52 +00:00
cat ./image/manifest.json | jq - r ' . [ 0 ] . Layers | . [ ] ' > layer-list
2020-05-15 21:57:56 +00:00
configName = " $ ( c a t . / i m a g e / m a n i f e s t . j s o n | j q - r ' . [ 0 ] . C o n f i g ' ) "
baseEnvs = " $ ( c a t " ./image / $ configName " | j q ' . c o n f i g . E n v / / [ ] ' ) "
2020-04-24 23:36:52 +00:00
2020-07-18 16:06:22 +00:00
# Extract the parentID from the manifest
if [ [ - n " $ f r o m I m a g e N a m e " ] ] && [ [ - n " $ f r o m I m a g e T a g " ] ] ; then
parentID = " $ (
cat " i m a g e / m a n i f e s t . j s o n " |
jq - r ' . [ ] | select ( . RepoTags | contains ( [ $ desiredTag ] ) ) | rtrimstr ( " . j s o n " ) ' \
- - arg desiredTag " $ f r o m I m a g e N a m e : $ f r o m I m a g e T a g "
) "
else
echo " F r o m - i m a g e n a m e o r t a g w a s n ' t s e t . R e a d i n g t h e f i r s t I D . "
parentID = " $ ( c a t " image/manifest.json " | j q - r ' . [ 0 ] . C o n f i g | r t r i m s t r ( " . json " ) ' ) "
fi
2020-05-15 21:57:56 +00:00
# Otherwise do not import the base image configuration and manifest
2020-04-24 23:36:52 +00:00
chmod a + w image image /* . j s o n
rm - f image /* . j s o n
for l in image /* / l a y e r . t a r ; d o
ls_tar $ l > > baseFiles
done
else
touch layer-list
fi
chmod - R ug + rw image
mkdir temp
cp $ { layer } /* t e m p /
chmod ug + w temp /*
for dep in $ ( cat $ layerClosure ) ; do
find $ dep > > layerFiles
done
echo " A d d i n g l a y e r . . . "
# Record the contents of the tarball with ls_tar.
ls_tar temp/layer.tar > > baseFiles
# Append nix/store directory to the layer so that when the layer is loaded in the
# image /nix/store has read permissions for non-root users.
# nix/store is added only if the layer has /nix/store paths in it.
if [ $ ( wc - l < $ layerClosure ) - gt 1 ] && [ $ ( grep - c - e " ^ / n i x / s t o r e $ " baseFiles ) - eq 0 ] ; then
mkdir - p nix/store
chmod - R 555 nix
echo " . / n i x " > > layerFiles
echo " . / n i x / s t o r e " > > layerFiles
fi
# Get the files in the new layer which were *not* present in
# the old layer, and record them as newFiles.
comm < ( sort - n baseFiles | uniq ) \
< ( sort - n layerFiles | uniq | grep - v $ { layer } ) -1 -3 > newFiles
# Append the new files to the layer.
tar - rpf temp/layer.tar - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " \
2021-09-22 15:38:15 +00:00
- - owner = 0 - - group = 0 - - no-recursion - - verbatim-files-from - - files-from newFiles
2020-04-24 23:36:52 +00:00
echo " A d d i n g m e t a . . . "
# If we have a parentID, add it to the json metadata.
if [ [ - n " $ p a r e n t I D " ] ] ; then
cat temp/json | jshon - s " $ p a r e n t I D " - i parent > tmpjson
mv tmpjson temp/json
fi
# Take the sha256 sum of the generated json and use it as the layer ID.
# Compute the size and add it to the json under the 'Size' field.
layerID = $ ( sha256sum temp/json | cut - d ' ' - f 1 )
size = $ ( stat - - printf = " % s " temp/layer.tar )
cat temp/json | jshon - s " $ l a y e r I D " - i id - n $ size - i Size > tmpjson
mv tmpjson temp/json
# Use the temp folder we've been working on to create a new image.
mv temp image / $ layerID
# Add the new layer ID to the end of the layer list
(
cat layer-list
# originally this used `sed -i "1i$layerID" layer-list`, but
# would fail if layer-list was completely empty.
echo " $ l a y e r I D / l a y e r . t a r "
) | sponge layer-list
# Create image json and image manifest
2020-05-15 21:57:56 +00:00
imageJson = $ ( cat $ { baseJson } | jq ' . config . Env = $ baseenv + . config . Env' - - argjson baseenv " $ b a s e E n v s " )
imageJson = $ ( echo " $ i m a g e J s o n " | jq " . + { \" r o o t f s \" : { \" d i f f _ i d s \" : [ ] , \" t y p e \" : \" l a y e r s \" } } " )
2020-04-24 23:36:52 +00:00
manifestJson = $ ( jq - n " [ { \" R e p o T a g s \" : [ \" $ i m a g e N a m e : $ i m a g e T a g \" ] } ] " )
for layerTar in $ ( cat ./layer-list ) ; do
layerChecksum = $ ( sha256sum image / $ layerTar | cut - d ' ' - f1 )
imageJson = $ ( echo " $ i m a g e J s o n " | jq " . h i s t o r y | = . + [ { \" c r e a t e d \" : \" $ ( j q - r . c r e a t e d ${ baseJson } ) \" } ] " )
# diff_ids order is from the bottom-most to top-most layer
imageJson = $ ( echo " $ i m a g e J s o n " | jq " . r o o t f s . d i f f _ i d s | = . + [ \" s h a 2 5 6 : $ l a y e r C h e c k s u m \" ] " )
manifestJson = $ ( echo " $ m a n i f e s t J s o n " | jq " . [ 0 ] . L a y e r s | = . + [ \" $ l a y e r T a r \" ] " )
done
imageJsonChecksum = $ ( echo " $ i m a g e J s o n " | sha256sum | cut - d ' ' - f1 )
echo " $ i m a g e J s o n " > " i m a g e / $ i m a g e J s o n C h e c k s u m . j s o n "
manifestJson = $ ( echo " $ m a n i f e s t J s o n " | jq " . [ 0 ] . C o n f i g = \" $ i m a g e J s o n C h e c k s u m . j s o n \" " )
echo " $ m a n i f e s t J s o n " > image/manifest.json
# Store the json under the name image/repositories.
jshon - n object \
- n object - s " $ l a y e r I D " - i " $ i m a g e T a g " \
- i " $ i m a g e N a m e " > image/repositories
# Make the image read-only.
chmod - R a-w image
echo " C o o k i n g t h e i m a g e . . . "
2024-02-29 20:09:43 +00:00
tar - C image - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - - owner = 0 - - group = 0 - - xform s:' ^ . / ' : : - c . | $ { compress . compress } > $ out
2020-04-24 23:36:52 +00:00
echo " F i n i s h e d . "
'' ;
in
2023-10-09 19:29:22 +00:00
checked result
) ;
2020-04-24 23:36:52 +00:00
2021-04-17 00:35:05 +00:00
# Merge the tarballs of images built with buildImage into a single
# tarball that contains all images. Running `docker load` on the resulting
# tarball will load the images into the docker daemon.
mergeImages = images : runCommand " m e r g e - d o c k e r - i m a g e s "
{
inherit images ;
2024-02-29 20:09:43 +00:00
nativeBuildInputs = [ file jq ]
++ compressors . none . nativeInputs
++ compressors . gz . nativeInputs
++ compressors . zstd . nativeInputs ;
2021-04-17 00:35:05 +00:00
} ''
mkdir image inputs
# Extract images
repos = ( )
manifests = ( )
2024-02-29 20:09:43 +00:00
last_image_mime = " a p p l i c a t i o n / g z i p "
2021-04-17 00:35:05 +00:00
for item in $ images ; do
name = $ ( basename $ item )
mkdir inputs / $ name
2024-02-29 20:09:43 +00:00
last_image_mime = $ ( file - - mime-type - b $ item )
case $ last_image_mime in
" a p p l i c a t i o n / x - t a r " ) $ { compressors . none . decompress } ; ;
" a p p l i c a t i o n / z s t d " ) $ { compressors . zstd . decompress } ; ;
" a p p l i c a t i o n / g z i p " ) $ { compressors . gz . decompress } ; ;
* ) echo " e r r o r : u n e x p e c t e d l a y e r t y p e $ l a s t _ i m a g e _ m i m e " > & 2 ; exit 1 ; ;
esac < $ item | tar - xC inputs / $ name
2021-04-17 00:35:05 +00:00
if [ - f inputs / $ name/repositories ] ; then
repos + = ( inputs / $ name/repositories )
fi
if [ - f inputs / $ name/manifest.json ] ; then
manifests + = ( inputs / $ name/manifest.json )
fi
done
# Copy all layers from input images to output image directory
2023-08-22 20:05:09 +00:00
cp - R - - update = none inputs /* / * i m a g e /
2021-04-17 00:35:05 +00:00
# Merge repositories objects and manifests
jq - s add " ' ' ${ repos [ @ ] } " > repositories
jq - s add " ' ' ${ manifests [ @ ] } " > manifest . json
# Replace output image repositories and manifest with merged versions
mv repositories image/repositories
mv manifest . json image/manifest.json
# Create tarball and gzip
2024-02-29 20:09:43 +00:00
tar - C image - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - - owner = 0 - - group = 0 - - xform s:' ^ . / ' : : - c . | (
case $ last_image_mime in
" a p p l i c a t i o n / x - t a r " ) $ { compressors . none . compress } ; ;
" a p p l i c a t i o n / z s t d " ) $ { compressors . zstd . compress } ; ;
" a p p l i c a t i o n / g z i p " ) $ { compressors . gz . compress } ; ;
# `*)` not needed; already checked.
esac
) > $ out
2021-04-17 00:35:05 +00:00
'' ;
2020-12-03 08:41:04 +00:00
# Provide a /etc/passwd and /etc/group that contain root and nobody.
# Useful when packaging binaries that insist on using nss to look up
# username/groups (like nginx).
# /bin/sh is fine to not exist, and provided by another shim.
2022-04-03 18:54:34 +00:00
inherit fakeNss ; # alias
2020-12-03 08:41:04 +00:00
2021-08-27 14:25:00 +00:00
# This provides a /usr/bin/env, for shell scripts using the
# "#!/usr/bin/env executable" shebang.
usrBinEnv = runCommand " u s r - b i n - e n v " { } ''
mkdir - p $ out/usr/bin
2022-04-15 01:41:22 +00:00
ln - s $ { coreutils } /bin/env $ out/usr/bin
2021-08-27 14:25:00 +00:00
'' ;
2020-12-03 08:41:04 +00:00
# This provides /bin/sh, pointing to bashInteractive.
2024-02-29 20:09:43 +00:00
# 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.
2021-08-10 14:31:46 +00:00
binSh = runCommand " b i n - s h " { } ''
2020-12-03 08:41:04 +00:00
mkdir - p $ out/bin
ln - s $ { bashInteractive } /bin/bash $ out/bin/sh
'' ;
2022-09-09 14:08:57 +00:00
# This provides the ca bundle in common locations
caCertificates = runCommand " c a - c e r t i f i c a t e s " { } ''
2022-09-22 12:36:57 +00:00
mkdir - p $ out/etc/ssl/certs $ out/etc/pki/tls/certs
2022-09-09 14:08:57 +00:00
# Old NixOS compatibility.
ln - s $ { cacert } /etc/ssl/certs/ca-bundle.crt $ out/etc/ssl/certs/ca-bundle.crt
# NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility.
ln - s $ { cacert } /etc/ssl/certs/ca-bundle.crt $ out/etc/ssl/certs/ca-certificates.crt
# CentOS/Fedora compatibility.
ln - s $ { cacert } /etc/ssl/certs/ca-bundle.crt $ out/etc/pki/tls/certs/ca-bundle.crt
'' ;
2020-04-24 23:36:52 +00:00
# Build an image and populate its nix database with the provided
# contents. The main purpose is to be able to use nix commands in
# the container.
# Be careful since this doesn't work well with multilayer.
2022-07-14 12:49:19 +00:00
# TODO: add the dependencies of the config json.
2024-07-27 06:49:29 +00:00
buildImageWithNixDb = args : buildImage ( args // { includeNixDB = true ; } ) ;
2020-05-03 17:38:23 +00:00
2024-07-27 06:49:29 +00:00
buildLayeredImageWithNixDb = args : buildLayeredImage ( args // { includeNixDB = true ; } ) ;
2020-05-03 17:38:23 +00:00
2024-02-29 20:09:43 +00:00
# Arguments are documented in ../../../doc/build-helpers/images/dockertools.section.md
2023-10-09 19:29:22 +00:00
streamLayeredImage = lib . makeOverridable (
2021-08-10 14:31:46 +00:00
{
name
2024-02-29 20:09:43 +00:00
, tag ? null
, fromImage ? null
, contents ? [ ]
, config ? { }
, architecture ? defaultArchitecture
, created ? " 1 9 7 0 - 0 1 - 0 1 T 0 0 : 0 0 : 0 1 Z "
, uid ? 0
, gid ? 0
, uname ? " r o o t "
, gname ? " r o o t "
, maxLayers ? 100
, extraCommands ? " "
, fakeRootCommands ? " "
, enableFakechroot ? false
, includeStorePaths ? true
2024-07-27 06:49:29 +00:00
, includeNixDB ? false
2024-02-29 20:09:43 +00:00
, passthru ? { }
2021-08-10 14:31:46 +00:00
,
} :
assert
2020-07-18 16:06:22 +00:00
( lib . assertMsg ( maxLayers > 1 )
2021-08-10 14:31:46 +00:00
" t h e m a x L a y e r s a r g u m e n t o f d o c k e r T o o l s . b u i l d L a y e r e d I m a g e f u n c t i o n m u s t b e g r e a t h e r t h a n 1 ( c u r r e n t v a l u e : ${ toString maxLayers } ) " ) ;
2024-07-27 06:49:29 +00:00
assert
( lib . assertMsg ( enableFakechroot -> ! stdenv . isDarwin ) ''
cannot use ` enableFakechroot ` because ` proot ` is not portable to Darwin . Workarounds :
- use ` fakeRootCommands ` with the restricted ` fakeroot ` environment
- cross-compile your packages
- run your packages in a virtual machine
Discussion : https://github.com/NixOS/nixpkgs/issues/327311'' ) ;
2021-08-10 14:31:46 +00:00
let
baseName = baseNameOf name ;
streamScript = writePython3 " s t r e a m " { } ./stream_layered_image.py ;
baseJson = writeText " ${ baseName } - b a s e . j s o n " ( builtins . toJSON {
2022-12-17 10:02:37 +00:00
inherit config architecture ;
2021-08-10 14:31:46 +00:00
os = " l i n u x " ;
} ) ;
contentsList = if builtins . isList contents then contents else [ contents ] ;
2023-08-22 20:05:09 +00:00
bind-paths = builtins . toString ( builtins . map ( path : " - - b i n d = ${ path } : ${ path } ! " ) [
" / d e v / "
" / p r o c / "
" / s y s / "
" ${ builtins . storeDir } / "
" $ o u t / l a y e r . t a r "
] ) ;
2021-08-10 14:31:46 +00:00
# We store the customisation layer as a tarball, to make sure that
2022-12-28 21:21:41 +00:00
# things like permissions set on 'extraCommands' are not overridden
2021-08-10 14:31:46 +00:00
# by Nix. Then we precompute the sha256 for performance.
customisationLayer = symlinkJoin {
name = " ${ baseName } - c u s t o m i s a t i o n - l a y e r " ;
paths = contentsList ;
2024-07-27 06:49:29 +00:00
extraCommands =
( lib . optionalString includeNixDB ( mkDbExtraCommand contents ) ) + extraCommands ;
inherit fakeRootCommands ;
2021-12-06 16:07:01 +00:00
nativeBuildInputs = [
fakeroot
] ++ optionals enableFakechroot [
2023-08-22 20:05:09 +00:00
proot
2021-12-06 16:07:01 +00:00
] ;
2021-08-10 14:31:46 +00:00
postBuild = ''
mv $ out old_out
( cd old_out ; eval " $ e x t r a C o m m a n d s " )
mkdir $ out
2024-01-02 11:29:13 +00:00
$ { if enableFakechroot then ''
2024-01-25 14:12:00 +00:00
proot - r $ PWD/old_out $ { bind-paths } - - pwd = / fakeroot bash - c '
2024-01-02 11:29:13 +00:00
source $ stdenv/setup
eval " $ f a k e R o o t C o m m a n d s "
tar \
- - sort name \
- - exclude = ./proc \
- - exclude = ./sys \
2024-02-29 20:09:43 +00:00
- - exclude = . ${ builtins . storeDir } \
2024-01-02 11:29:13 +00:00
- - numeric-owner - - mtime " @ $ S O U R C E _ D A T E _ E P O C H " \
- - hard-dereference \
- cf $ out/layer.tar .
'
'' e l s e ''
fakeroot bash - c '
source $ stdenv/setup
cd old_out
eval " $ f a k e R o o t C o m m a n d s "
tar \
- - sort name \
- - numeric-owner - - mtime " @ $ S O U R C E _ D A T E _ E P O C H " \
- - hard-dereference \
- cf $ out/layer.tar .
'
'' }
2021-08-10 14:31:46 +00:00
sha256sum $ out/layer.tar \
| cut - f 1 - d ' ' \
> $ out/checksum
'' ;
} ;
2020-07-18 16:06:22 +00:00
2021-08-10 14:31:46 +00:00
closureRoots = lib . optionals includeStorePaths /* n o r m a l l y t r u e */ (
2021-12-06 16:07:01 +00:00
[ baseJson customisationLayer ]
2021-08-10 14:31:46 +00:00
) ;
overallClosure = writeText " c l o s u r e " ( lib . concatStringsSep " " closureRoots ) ;
# These derivations are only created as implementation details of docker-tools,
# so they'll be excluded from the created images.
2021-12-06 16:07:01 +00:00
unnecessaryDrvs = [ baseJson overallClosure customisationLayer ] ;
2021-08-10 14:31:46 +00:00
conf = runCommand " ${ baseName } - c o n f . j s o n "
{
2024-02-29 20:09:43 +00:00
inherit fromImage maxLayers created uid gid uname gname ;
2021-08-10 14:31:46 +00:00
imageName = lib . toLower name ;
2022-11-27 09:42:12 +00:00
preferLocalBuild = true ;
2021-08-10 14:31:46 +00:00
passthru . imageTag =
if tag != null
then tag
else
2024-04-21 15:54:59 +00:00
lib . head ( lib . strings . splitString " - " ( baseNameOf ( builtins . unsafeDiscardStringContext conf . outPath ) ) ) ;
2021-08-10 14:31:46 +00:00
paths = buildPackages . referencesByPopularity overallClosure ;
nativeBuildInputs = [ jq ] ;
} ''
$ { if ( tag == null ) then ''
outName = " $ ( b a s e n a m e " $ out " ) "
outHash = $ ( echo " $ o u t N a m e " | cut - d - - f 1 )
imageTag = $ outHash
'' e l s e ''
imageTag = " ${ tag } "
'' }
# convert "created" to iso format
if [ [ " $ c r e a t e d " != " n o w " ] ] ; then
created = " $ ( d a t e - I s e c o n d s - d " $ created " ) "
fi
2020-07-18 16:06:22 +00:00
2021-08-10 14:31:46 +00:00
paths ( ) {
cat $ paths $ { lib . concatMapStringsSep " "
( path : " | ( g r e p - v ${ path } | | t r u e ) " )
unnecessaryDrvs }
}
2020-09-25 04:45:31 +00:00
2021-08-10 14:31:46 +00:00
# Compute the number of layers that are already used by a potential
# 'fromImage' as well as the customization layer. Ensure that there is
# still at least one layer available to store the image contents.
usedLayers = 0
2021-03-20 04:20:00 +00:00
2021-08-10 14:31:46 +00:00
# subtract number of base image layers
if [ [ - n " $ f r o m I m a g e " ] ] ; then
( ( usedLayers + = $ ( tar - xOf " $ f r o m I m a g e " manifest . json | jq ' . [ 0 ] . Layers | length' ) ) )
fi
2021-03-20 04:20:00 +00:00
2021-08-10 14:31:46 +00:00
# one layer will be taken up by the customisation layer
( ( usedLayers + = 1 ) )
2021-03-20 04:20:00 +00:00
2021-08-10 14:31:46 +00:00
if ! ( ( $ usedLayers < $ maxLayers ) ) ; then
echo > & 2 " E r r o r : u s e d L a y e r s $ u s e d L a y e r s l a y e r s t o s t o r e ' f r o m I m a g e ' a n d " \
" ' e x t r a C o m m a n d s ' , b u t o n l y m a x L a y e r s = $ m a x L a y e r s w e r e " \
" a l l o w e d . A t l e a s t 1 l a y e r i s r e q u i r e d t o s t o r e c o n t e n t s . "
exit 1
fi
availableLayers = $ ( ( maxLayers - usedLayers ) )
# Create $maxLayers worth of Docker Layers, one layer per store path
# unless there are more paths than $maxLayers. In that case, create
# $maxLayers-1 for the most popular layers, and smush the remainaing
# store paths in to one final layer.
#
# The following code is fiddly w.r.t. ensuring every layer is
# created, and that no paths are missed. If you change the
# following lines, double-check that your code behaves properly
# when the number of layers equals:
# maxLayers-1, maxLayers, and maxLayers+1, 0
2022-10-21 18:38:19 +00:00
paths |
jq - sR '
rtrimstr ( " \n " ) | split ( " \n " )
| ( . [ : $ maxLayers-1 ] | map ( [ . ] ) ) + [ . [ $ maxLayers-1 : ] ]
| map ( select ( length > 0 ) )
2021-08-10 14:31:46 +00:00
' \
2022-10-21 18:38:19 +00:00
- - argjson maxLayers " $ a v a i l a b l e L a y e r s " > store_layers . json
2021-03-20 04:20:00 +00:00
2022-10-21 18:38:19 +00:00
# The index on $store_layers is necessary because the --slurpfile
# automatically reads the file as an array.
2021-08-10 14:31:46 +00:00
cat $ { baseJson } | jq '
. + {
" s t o r e _ d i r " : $ store_dir ,
" f r o m _ i m a g e " : $ from_image ,
2022-10-21 18:38:19 +00:00
" s t o r e _ l a y e r s " : $ store_layers [ 0 ] ,
2021-08-10 14:31:46 +00:00
" c u s t o m i s a t i o n _ l a y e r " , $ customisation_layer ,
" r e p o _ t a g " : $ repo_tag ,
2024-02-29 20:09:43 +00:00
" c r e a t e d " : $ created ,
" u i d " : $ uid ,
" g i d " : $ gid ,
" u n a m e " : $ uname ,
" g n a m e " : $ gname
2021-08-10 14:31:46 +00:00
}
' - - arg store_dir " ${ storeDir } " \
- - argjson from_image $ { if fromImage == null then " n u l l " else " ' \" ${ fromImage } \" ' " } \
2022-10-21 18:38:19 +00:00
- - slurpfile store_layers store_layers . json \
2021-08-10 14:31:46 +00:00
- - arg customisation_layer $ { customisationLayer } \
- - arg repo_tag " $ i m a g e N a m e : $ i m a g e T a g " \
2024-02-29 20:09:43 +00:00
- - arg created " $ c r e a t e d " \
- - arg uid " $ u i d " \
- - arg gid " $ g i d " \
- - arg uname " $ u n a m e " \
- - arg gname " $ g n a m e " |
2021-08-10 14:31:46 +00:00
tee $ out
'' ;
2022-10-21 18:38:19 +00:00
2021-08-10 14:31:46 +00:00
result = runCommand " s t r e a m - ${ baseName } "
{
2024-07-27 06:49:29 +00:00
inherit conf ;
2021-08-10 14:31:46 +00:00
inherit ( conf ) imageName ;
2024-07-27 06:49:29 +00:00
inherit streamScript ;
2022-11-27 09:42:12 +00:00
preferLocalBuild = true ;
2022-02-10 20:34:41 +00:00
passthru = passthru // {
2021-08-10 14:31:46 +00:00
inherit ( conf ) imageTag ;
# Distinguish tarballs and exes at the Nix level so functions that
# take images can know in advance how the image is supposed to be used.
isExe = true ;
} ;
nativeBuildInputs = [ makeWrapper ] ;
} ''
2024-07-27 06:49:29 +00:00
makeWrapper $ streamScript $ out - - add-flags $ conf
2021-08-10 14:31:46 +00:00
'' ;
in
2023-10-09 19:29:22 +00:00
result
) ;
2022-11-27 09:42:12 +00:00
# This function streams a docker image that behaves like a nix-shell for a derivation
streamNixShellImage =
{ # The derivation whose environment this docker image should be based on
drv
, # Image Name
name ? drv . name + " - e n v "
, # Image tag, the Nix's output hash will be used if null
tag ? null
, # User id to run the container as. Defaults to 1000, because many
# binaries don't like to be run as root
uid ? 1000
, # Group id to run the container as, see also uid
gid ? 1000
, # The home directory of the user
homeDirectory ? " / b u i l d "
, # The path to the bash binary to use as the shell. See `NIX_BUILD_SHELL` in `man nix-shell`
shell ? bashInteractive + " / b i n / b a s h "
, # Run this command in the environment of the derivation, in an interactive shell. See `--command` in `man nix-shell`
command ? null
, # Same as `command`, but runs the command in a non-interactive shell instead. See `--run` in `man nix-shell`
run ? null
} :
assert lib . assertMsg ( ! ( drv . drvAttrs . __structuredAttrs or false ) )
" s t r e a m N i x S h e l l I m a g e : D o e s n o t w o r k w i t h t h e d e r i v a t i o n ${ drv . name } b e c a u s e i t u s e s _ _ s t r u c t u r e d A t t r s " ;
assert lib . assertMsg ( command == null || run == null )
" s t r e a m N i x S h e l l I m a g e : C a n ' t s p e c i f y b o t h c o m m a n d a n d r u n " ;
let
# A binary that calls the command to build the derivation
builder = writeShellScriptBin " b u i l d D e r i v a t i o n " ''
2024-07-27 06:49:29 +00:00
exec $ { lib . escapeShellArg ( valueToString drv . drvAttrs . builder ) } $ { lib . escapeShellArgs ( map valueToString drv . drvAttrs . args ) }
2022-11-27 09:42:12 +00:00
'' ;
staticPath = " ${ dirOf shell } : ${ lib . makeBinPath [ builder ] } " ;
# https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L493-L526
rcfile = writeText " n i x - s h e l l - r c " ''
unset PATH
dontAddDisableDepTrack = 1
# TODO: https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L506
[ - e $ stdenv/setup ] && source $ stdenv/setup
PATH = $ { staticPath }: " $ P A T H "
SHELL = $ { lib . escapeShellArg shell }
BASH = $ { lib . escapeShellArg shell }
set + e
[ - n " $ P S 1 " - a - z " $ N I X _ S H E L L _ P R E S E R V E _ P R O M P T " ] && PS1 = ' \ n \ [ \ 033 [ 1 ; 3 2 m \ ] [ nix-shell : \ w ] \ $ \ [ \ 033 [ 0 m \ ] '
if [ " $ ( t y p e - t r u n H o o k ) " = function ] ; then
runHook shellHook
fi
unset NIX_ENFORCE_PURITY
shopt - u nullglob
shopt - s execfail
$ { optionalString ( command != null || run != null ) ''
$ { optionalString ( command != null ) command }
$ { optionalString ( run != null ) run }
exit
'' }
'' ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/globals.hh#L464-L465
sandboxBuildDir = " / b u i l d " ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L992-L1004
drvEnv = lib . mapAttrs' ( name : value :
2024-07-27 06:49:29 +00:00
let str = valueToString value ;
2022-11-27 09:42:12 +00:00
in if lib . elem name ( drv . drvAttrs . passAsFile or [ ] )
then lib . nameValuePair " ${ name } P a t h " ( writeText " p a s s - a s - t e x t - ${ name } " str )
else lib . nameValuePair name str
) drv . drvAttrs //
# A mapping from output name to the nix store path where they should end up
# https://github.com/NixOS/nix/blob/2.8.0/src/libexpr/primops.cc#L1253
lib . genAttrs drv . outputs ( output : builtins . unsafeDiscardStringContext drv . ${ output } . outPath ) ;
# Environment variables set in the image
envVars = {
# Root certificates for internet access
SSL_CERT_FILE = " ${ cacert } / e t c / s s l / c e r t s / c a - b u n d l e . c r t " ;
2024-04-21 15:54:59 +00:00
NIX_SSL_CERT_FILE = " ${ cacert } / e t c / s s l / c e r t s / c a - b u n d l e . c r t " ;
2022-11-27 09:42:12 +00:00
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1027-L1030
# PATH = "/path-not-set";
# Allows calling bash and `buildDerivation` as the Cmd
PATH = staticPath ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1032-L1038
HOME = homeDirectory ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1040-L1044
NIX_STORE = storeDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1046-L1047
# TODO: Make configurable?
NIX_BUILD_CORES = " 1 " ;
} // drvEnv // {
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1008-L1010
NIX_BUILD_TOP = sandboxBuildDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1012-L1013
TMPDIR = sandboxBuildDir ;
TEMPDIR = sandboxBuildDir ;
TMP = sandboxBuildDir ;
TEMP = sandboxBuildDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1015-L1019
PWD = sandboxBuildDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1071-L1074
# We don't set it here because the output here isn't handled in any special way
# NIX_LOG_FD = "2";
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1076-L1077
TERM = " x t e r m - 2 5 6 c o l o r " ;
} ;
in streamLayeredImage {
inherit name tag ;
contents = [
binSh
usrBinEnv
( fakeNss . override {
# Allows programs to look up the build user's home directory
# https://github.com/NixOS/nix/blob/ffe155abd36366a870482625543f9bf924a58281/src/libstore/build/local-derivation-goal.cc#L906-L910
# Slightly differs however: We use the passed-in homeDirectory instead of sandboxBuildDir.
# We're doing this because it's arguably a bug in Nix that sandboxBuildDir is used here: https://github.com/NixOS/nix/issues/6379
extraPasswdLines = [
" n i x b l d : x : ${ toString uid } : ${ toString gid } : B u i l d u s e r : ${ homeDirectory } : / n o s h e l l "
] ;
extraGroupLines = [
" n i x b l d : ! : ${ toString gid } : "
] ;
} )
] ;
fakeRootCommands = ''
# Effectively a single-user installation of Nix, giving the user full
# control over the Nix store. Needed for building the derivation this
# shell is for, but also in case one wants to use Nix inside the
# image
mkdir - p ./nix / { store , var/nix } ./etc/nix
chown - R $ { toString uid }: $ { toString gid } ./nix ./etc/nix
# Gives the user control over the build directory
mkdir - p . ${ sandboxBuildDir }
chown - R $ { toString uid }: $ { toString gid } . ${ sandboxBuildDir }
'' ;
# Run this image as the given uid/gid
config . User = " ${ toString uid } : ${ toString gid } " ;
config . Cmd =
# https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L185-L186
# https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L534-L536
if run == null
then [ shell " - - r c f i l e " rcfile ]
else [ shell rcfile ] ;
config . WorkingDir = sandboxBuildDir ;
config . Env = lib . mapAttrsToList ( name : value : " ${ name } = ${ value } " ) envVars ;
} ;
# Wrapper around streamNixShellImage to build an image from the result
2024-02-29 20:09:43 +00:00
buildNixShellImage = { drv , compressor ? " g z " , . . . } @ args :
2022-11-27 09:42:12 +00:00
let
2024-04-21 15:54:59 +00:00
stream = streamNixShellImage ( builtins . removeAttrs args [ " c o m p r e s s o r " ] ) ;
2024-02-29 20:09:43 +00:00
compress = compressorForImage compressor drv . name ;
2022-11-27 09:42:12 +00:00
in
2024-02-29 20:09:43 +00:00
runCommand " ${ drv . name } - e n v . t a r ${ compress . ext } "
2022-11-27 09:42:12 +00:00
{
inherit ( stream ) imageName ;
passthru = { inherit ( stream ) imageTag ; } ;
2024-02-29 20:09:43 +00:00
nativeBuildInputs = compress . nativeInputs ;
} " ${ stream } | ${ compress . compress } > $ o u t " ;
2020-04-24 23:36:52 +00:00
}