busybox/Dockerfile-builder.template

498 lines
15 KiB
Docker

{{ if env.variant == "musl" then ( -}}
FROM alpine:3.22
RUN set -eux; \
apk add --no-cache \
bzip2 \
coreutils \
curl \
gcc \
gnupg \
linux-headers \
make \
musl-dev \
patch \
tzdata \
# busybox's tar ironically does not maintain mtime of directories correctly (which we need for SOURCE_DATE_EPOCH / reproducibility)
tar \
;
{{ ) else ( -}}
FROM debian:trixie-slim
RUN set -eux; \
apt-get install --update -y \
bzip2 \
curl \
gcc \
gnupg \
make \
patch \
; \
apt-get dist-clean
{{ ) end -}}
{{ if env.variant == "uclibc" then ( -}}
# grab/use buildroot for its uClibc toolchain
RUN set -eux; \
apt-get install --update -y \
bc \
cpio \
dpkg-dev \
file \
g++ \
perl \
python3 \
rsync \
unzip \
wget \
; \
apt-get dist-clean
# pub dsa1024 2009-01-15 [SC]
# AB07 D806 D2CE 741F B886 EE50 B025 BA8B 59C3 6319
# uid [ unknown] Peter Korsgaard <jacmet@uclibc.org>
# sub elg2048 2009-01-15 [E]
RUN mkdir -p ~/.gnupg && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys AB07D806D2CE741FB886EE50B025BA8B59C36319
# https://buildroot.org/download.html
# https://buildroot.org/downloads/?C=M;O=D
ENV BUILDROOT_VERSION {{ .buildroot.version }}
RUN set -eux; \
tarball="buildroot-${BUILDROOT_VERSION}.tar.xz"; \
curl -fL -o buildroot.tar.xz "https://buildroot.org/downloads/$tarball"; \
curl -fL -o buildroot.tar.xz.sign "https://buildroot.org/downloads/$tarball.sign"; \
gpg --batch --decrypt --output buildroot.tar.xz.txt buildroot.tar.xz.sign; \
awk '$1 == "SHA1:" && $2 ~ /^[0-9a-f]+$/ && $3 == "'"$tarball"'" { print $2, "*buildroot.tar.xz" }' buildroot.tar.xz.txt > buildroot.tar.xz.sha1; \
test -s buildroot.tar.xz.sha1; \
sha1sum -c buildroot.tar.xz.sha1; \
mkdir -p /usr/src/buildroot; \
tar -xf buildroot.tar.xz -C /usr/src/buildroot --strip-components 1; \
rm buildroot.tar.xz*
RUN set -eux; \
\
cd /usr/src/buildroot; \
\
setConfs=' \
BR2_STATIC_LIBS=y \
BR2_TOOLCHAIN_BUILDROOT_UCLIBC=y \
BR2_TOOLCHAIN_BUILDROOT_WCHAR=y \
'; \
\
unsetConfs=' \
BR2_SHARED_LIBS \
BR2_TOOLCHAIN_BUILDROOT_GLIBC \
'; \
\
# buildroot arches: https://gitlab.com/buildroot.org/buildroot/-/tree/HEAD/arch
# buildroot+uclibc arches: https://gitlab.com/buildroot.org/buildroot/-/blob/HEAD/package/uclibc/Config.in ("config BR2_PACKAGE_UCLIBC_ARCH_SUPPORTS")
dpkgArch="$(dpkg --print-architecture)"; \
case "$dpkgArch" in \
# explicitly target amd64 v1
amd64) \
setConfs="$setConfs \
BR2_x86_64=y \
BR2_x86_x86_64=y \
"; \
;; \
\
arm64) \
setConfs="$setConfs \
BR2_aarch64=y \
"; \
# https://github.com/docker-library/busybox/issues/149
setConfs="$setConfs BR2_ARM64_PAGE_SIZE_64K=y"; \
unsetConfs="$unsetConfs BR2_ARM64_PAGE_SIZE_4K"; \
# (it's reasonable to use a larger page size than the host, but not the reverse, and some distros default to 64k instead of 4k)
;; \
\
# https://wiki.debian.org/ArmEabiPort#Choice_of_minimum_CPU
# https://github.com/free-electrons/toolchains-builder/blob/db259641eaf5bbcf13f4a3c5003e5436e806770c/configs/arch/armv5-eabi.config
# https://gitlab.com/buildroot.org/buildroot/-/blob/HEAD/arch/Config.in.arm
# (Debian minimums at ARMv4, we minimum at ARMv5 instead)
armel) \
setConfs="$setConfs \
BR2_arm=y \
BR2_arm926t=y \
BR2_ARM_EABI=y \
BR2_ARM_INSTRUCTIONS_THUMB=y \
BR2_ARM_SOFT_FLOAT=y \
"; \
;; \
\
# "Currently the Debian armhf port requires at least an ARMv7 CPU with Thumb-2 and VFP3D16."
# https://wiki.debian.org/ArmHardFloatPort#Supported_devices
# https://github.com/free-electrons/toolchains-builder/blob/db259641eaf5bbcf13f4a3c5003e5436e806770c/configs/arch/armv7-eabihf.config
# https://gitlab.com/buildroot.org/buildroot/-/blob/HEAD/arch/Config.in.arm
armhf) \
setConfs="$setConfs \
BR2_arm=y \
BR2_cortex_a9=y \
BR2_ARM_EABIHF=y \
BR2_ARM_ENABLE_VFP=y \
BR2_ARM_FPU_VFPV3D16=y \
BR2_ARM_INSTRUCTIONS_THUMB2=y \
"; \
unsetConfs="$unsetConfs BR2_ARM_SOFT_FLOAT"; \
;; \
\
i386) \
setConfs="$setConfs \
BR2_i386=y \
"; \
;; \
\
mips64el) \
setConfs="$setConfs \
BR2_mips64el=y \
BR2_mips_64r2=y \
BR2_MIPS_NABI64=y \
"; \
unsetConfs="$unsetConfs \
BR2_MIPS_SOFT_FLOAT \
"; \
;; \
\
# TODO ppc64el ? (needs BR2_TOOLCHAIN_BUILDROOT_UCLIBC support)
\
riscv64) \
setConfs="$setConfs \
BR2_riscv=y \
BR2_RISCV_64=y \
"; \
;; \
\
# TODO s390x ? (needs BR2_TOOLCHAIN_BUILDROOT_UCLIBC support)
\
*) \
echo >&2 "error: unsupported architecture '$dpkgArch'!"; \
exit 1; \
;; \
esac; \
if [ "$dpkgArch" != 'i386' ]; then \
unsetConfs="$unsetConfs BR2_i386"; \
fi; \
\
make defconfig; \
\
for conf in $unsetConfs; do \
sed -i \
-e "s!^$conf=.*\$!# $conf is not set!" \
.config; \
done; \
\
for confV in $setConfs; do \
conf="${confV%=*}"; \
sed -i \
-e "s!^$conf=.*\$!$confV!" \
-e "s!^# $conf is not set\$!$confV!" \
.config; \
if ! grep -q "^$confV\$" .config; then \
echo "$confV" >> .config; \
fi; \
done; \
\
make oldconfig; \
\
# trust, but verify
for conf in $unsetConfs; do \
! grep -q "^$conf=" .config; \
done; \
for confV in $setConfs; do \
grep -q "^$confV\$" .config; \
done;
# https://www.finnie.org/2014/02/13/compiling-busybox-with-uclibc/
RUN set -eux; \
# force a particular GNU arch for "host-gmp" (otherwise it fails on some arches)
gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \
make -C /usr/src/buildroot \
HOST_GMP_CONF_OPTS="--build='"$gnuArch"'" \
# building host-tar:
# configure: error: you should not run configure as root (set FORCE_UNSAFE_CONFIGURE=1 in environment to bypass this check)
FORCE_UNSAFE_CONFIGURE=1 \
-j "$(nproc)" \
toolchain
ENV PATH /usr/src/buildroot/output/host/usr/bin:$PATH
{{ ) else "" end -}}
# pub 1024D/ACC9965B 2006-12-12
# Key fingerprint = C9E9 416F 76E6 10DB D09D 040F 47B7 0C55 ACC9 965B
# uid Denis Vlasenko <vda.linux@googlemail.com>
# sub 1024g/2C766641 2006-12-12
RUN mkdir -p ~/.gnupg && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys C9E9416F76E610DBD09D040F47B70C55ACC9965B
# https://busybox.net: {{ .date }}
ENV BUSYBOX_VERSION {{ .version }}
ENV BUSYBOX_SHA256 {{ .sha256 }}
RUN set -eux; \
tarball="busybox-${BUSYBOX_VERSION}.tar.bz2"; \
curl -fL -o busybox.tar.bz2.sig "https://busybox.net/downloads/$tarball.sig"; \
curl -fL -o busybox.tar.bz2 "https://busybox.net/downloads/$tarball"; \
echo "$BUSYBOX_SHA256 *busybox.tar.bz2" | sha256sum -c -; \
gpg --batch --verify busybox.tar.bz2.sig busybox.tar.bz2; \
# Alpine... 😅
mkdir -p /usr/src; \
tar -xf busybox.tar.bz2 -C /usr/src "busybox-$BUSYBOX_VERSION"; \
mv "/usr/src/busybox-$BUSYBOX_VERSION" /usr/src/busybox; \
rm busybox.tar.bz2*; \
\
# save the tarball's filesystem timestamp persistently (in case building busybox modifies it) so we can use it for reproducible rootfs later
SOURCE_DATE_EPOCH="$(stat -c '%Y' /usr/src/busybox | tee /usr/src/busybox.SOURCE_DATE_EPOCH)"; \
date="$(date -d "@$SOURCE_DATE_EPOCH" '+%Y%m%d%H%M.%S')"; \
touch -t "$date" /usr/src/busybox.SOURCE_DATE_EPOCH; \
# for logging validation/edification
date --date "@$SOURCE_DATE_EPOCH" --rfc-2822
WORKDIR /usr/src/busybox
{{
(
[
"no-cbq.patch",
if .version | startswith("1.36.") then empty else
"sha1_process_block64_shaNI.patch"
end,
empty # trailing comma
]
| map("/.patches/" + .)
) as $patches
| if $patches | length > 0 then (
-}}
# apply necessary/minimal patches (see /.patches/ in the top level of the repository)
COPY \
{{ $patches | map( -}}
{{ . }} \
{{ ) | join("") -}}
./.patches/
RUN set -eux; \
for patch in .patches/*.patch; do \
patch -p1 --input="$patch"; \
done; \
rm -rf .patches
{{ ) else "" end -}}
RUN set -eux; \
\
# build date/time gets embedded in the BusyBox binary -- SOURCE_DATE_EPOCH should override that
SOURCE_DATE_EPOCH="$(cat /usr/src/busybox.SOURCE_DATE_EPOCH)"; \
export SOURCE_DATE_EPOCH; \
# (has to be set in the config stage for making sure "AUTOCONF_TIMESTAMP" is embedded correctly)
\
setConfs=' \
CONFIG_AR=y \
CONFIG_FEATURE_AR_CREATE=y \
CONFIG_FEATURE_AR_LONG_FILENAMES=y \
# CONFIG_LAST_SUPPORTED_WCHAR: see https://github.com/docker-library/busybox/issues/13 (UTF-8 input)
CONFIG_LAST_SUPPORTED_WCHAR=0 \
{{ if env.variant == "glibc" then ( -}}
# As long as we rely on libnss (see below), we have to have libc.so anyhow, so we've removed CONFIG_STATIC here... 😭
{{ ) else ( -}}
CONFIG_STATIC=y \
{{ ) end -}}
'; \
\
unsetConfs=' \
CONFIG_FEATURE_SYNC_FANCY \
{{ if env.variant == "musl" then ( -}}
\
# see https://wiki.musl-libc.org/wiki/Building_Busybox
CONFIG_FEATURE_HAVE_RPC \
CONFIG_FEATURE_INETD_RPC \
CONFIG_FEATURE_UTMP \
CONFIG_FEATURE_WTMP \
{{ ) else "" end -}}
'; \
\
make defconfig; \
\
for conf in $unsetConfs; do \
sed -i \
-e "s!^$conf=.*\$!# $conf is not set!" \
.config; \
done; \
\
for confV in $setConfs; do \
conf="${confV%=*}"; \
sed -i \
-e "s!^$conf=.*\$!$confV!" \
-e "s!^# $conf is not set\$!$confV!" \
.config; \
if ! grep -q "^$confV\$" .config; then \
echo "$confV" >> .config; \
fi; \
done; \
\
make oldconfig; \
\
# trust, but verify
for conf in $unsetConfs; do \
! grep -q "^$conf=" .config; \
done; \
for confV in $setConfs; do \
grep -q "^$confV\$" .config; \
done
RUN set -eux; \
nproc="$(nproc)"; \
# https://git.busybox.net/busybox/tree/Makefile?h=1_37_stable#n145
{{ if env.variant == "uclibc" then ( -}}
CROSS_COMPILE="$(basename /usr/src/buildroot/output/host/usr/*-buildroot-linux-uclibc*)"; \
export CROSS_COMPILE="$CROSS_COMPILE-"; \
{{ ) else ( -}}
# we need to override SUBARCH explicitly (via ARCH) to avoid "uname -m" which gives the wrong answer for builds like i386 on an amd64 machine because kernel architecture != userspace architecture
# see https://git.busybox.net/busybox/tree/arch?h=1_37_stable#n145 for the only important values this *has* to match (everything else is best-effort, and needs to match the munging in https://git.busybox.net/busybox/tree/Makefile?h=1_37_stable#n185)
distroArch="$({{ if env.variant == "musl" then "apk --print-arch" else "dpkg --print-architecture" end }})"; \
case "$distroArch" in \
amd64 | x86_64) ARCH='x86_64' ;; \
arm64 | aarch64) ARCH='aarch64' ;; \
armhf | armel | armv*) ARCH='arm' ;; \
i386 | x86) ARCH='i386' ;; \
mips*) ARCH='mips' ;; \
ppc*) ARCH='powerpc' ;; \
riscv64) ARCH='riscv64' ;; \
s390x) ARCH='s390' ;; \
*) echo >&2 "error: unknown architecture: '$distroArch'"; exit 1 ;; \
esac; \
[ -n "$ARCH" ]; \
export ARCH; \
{{ ) end -}}
make -j "$nproc" busybox; \
./busybox --help; \
mkdir -p rootfs/bin; \
ln -vL busybox rootfs/bin/; \
\
{{ if env.variant == "uclibc" then ( -}}
# copy "getconf" from buildroot
ln -vL ../buildroot/output/target/usr/bin/getconf rootfs/bin/; \
{{ ) elif env.variant == "glibc" then ( -}}
# copy "getconf" from Debian
getconf="$(which getconf)"; \
ln -vL "$getconf" rootfs/bin/getconf; \
\
# hack hack hack hack hack
# with glibc, busybox (static or not) uses libnss for DNS resolution :(
mkdir -p rootfs/etc; \
cp /etc/nsswitch.conf rootfs/etc/; \
mkdir -p rootfs/lib; \
ln -sT lib rootfs/lib64; \
gccMultiarch="$(gcc -print-multiarch)"; \
set -- \
rootfs/bin/busybox \
rootfs/bin/getconf \
/lib/"$gccMultiarch"/libnss*.so.* \
# libpthread is part of glibc: https://stackoverflow.com/a/11210463/433558
/lib/"$gccMultiarch"/libpthread*.so.* \
; \
while [ "$#" -gt 0 ]; do \
f="$1"; shift; \
fn="$(basename "$f")"; \
if [ -e "rootfs/lib/$fn" ]; then continue; fi; \
if [ "${f#rootfs/}" = "$f" ]; then \
if [ "${fn#ld-}" = "$fn" ]; then \
ln -vL "$f" "rootfs/lib/$fn"; \
else \
cp -v "$f" "rootfs/lib/$fn"; \
fi; \
fi; \
ldd="$(ldd "$f" | awk ' \
$1 ~ /^\// { print $1; next } \
$2 == "=>" && $3 ~ /^\// { print $3; next } \
')"; \
set -- "$@" $ldd; \
done; \
{{ ) elif env.variant == "musl" then ( -}}
# copy simplified getconf port from Alpine
# https://github.com/alpinelinux/aports/commits/HEAD/main/musl/getconf.c
curl -fsSL \
"https://github.com/alpinelinux/aports/raw/48b16204aeeda5bc1f87e49c6b8e23d9abb07c73/main/musl/getconf.c" \
-o /usr/src/getconf.c \
; \
echo 'd87d0cbb3690ae2c5d8cc218349fd8278b93855dd625deaf7ae50e320aad247c */usr/src/getconf.c' | sha256sum -c -; \
gcc -o rootfs/bin/getconf -static -Os /usr/src/getconf.c; \
{{ ) else "" end -}}
chroot rootfs /bin/getconf _NPROCESSORS_ONLN; \
\
# TODO make this create symlinks instead so the output tarball is cleaner (but "-s" outputs absolute symlinks which is kind of annoying to deal with -- we should also consider letting busybox determine the "install paths"; see "busybox --list-full")
chroot rootfs /bin/busybox --install /bin
# install a few extra files from buildroot (/etc/passwd, etc)
RUN set -eux; \
{{ if env.variant == "uclibc" then "" else ( -}}
buildrootVersion={{ .buildroot.version | @sh }}; \
for file in \
system/device_table.txt \
system/skeleton/etc/group \
system/skeleton/etc/passwd \
system/skeleton/etc/shadow \
; do \
dir="$(dirname "$file")"; \
mkdir -p "../buildroot/$dir"; \
curl -fL -o "../buildroot/$file" "https://gitlab.com/buildroot.org/buildroot/-/raw/$buildrootVersion/$file"; \
[ -s "../buildroot/$file" ]; \
done; \
\
{{ ) end -}}
mkdir -p rootfs/etc; \
ln -vL \
../buildroot/system/skeleton/etc/group \
../buildroot/system/skeleton/etc/passwd \
../buildroot/system/skeleton/etc/shadow \
rootfs/etc/ \
; \
# CVE-2019-5021, https://github.com/docker-library/official-images/pull/5880#issuecomment-490681907
grep -E '^root::' rootfs/etc/shadow; \
sed -ri -e 's/^root::/root:*:/' rootfs/etc/shadow; \
grep -E '^root:[*]:' rootfs/etc/shadow; \
# set expected permissions, etc too (https://gitlab.com/buildroot.org/buildroot/-/blob/HEAD/system/device_table.txt)
awk ' \
!/^#/ { \
if ($2 != "d" && $2 != "f") { \
printf "error: unknown type \"%s\" encountered in line %d: %s\n", $2, NR, $0 > "/dev/stderr"; \
exit 1; \
} \
sub(/^\/?/, "rootfs/", $1); \
if ($2 == "d") { \
printf "mkdir -p %s\n", $1; \
} \
printf "chmod %s %s\n", $3, $1; \
} \
' ../buildroot/system/device_table.txt | sh -eux
# create missing home directories and ensure /usr/bin/env exists
RUN set -eux; \
cd rootfs; \
for userHome in $(awk -F ':' '{ print $3 ":" $4 "=" $6 }' etc/passwd); do \
user="${userHome%%=*}"; \
home="${userHome#*=}"; \
home="./${home#/}"; \
if [ ! -d "$home" ]; then \
mkdir -p "$home"; \
chown "$user" "$home"; \
chmod 755 "$home"; \
fi; \
done; \
if [ ! -s usr/bin/env ] && [ -s bin/env ]; then \
mkdir -p usr/bin; \
ln -s ../../bin/env usr/bin/; \
fi
# test and make sure it works
RUN chroot rootfs /usr/bin/env sh -xec 'true'
# ensure correct timezone (UTC)
RUN set -eux; \
ln -vL /usr/share/zoneinfo/UTC rootfs/etc/localtime; \
[ "$(chroot rootfs date +%Z)" = 'UTC' ]
# test and make sure DNS works too
RUN set -eux; \
cp -L /etc/resolv.conf rootfs/etc/; \
chroot rootfs /bin/sh -xec 'nslookup google.com'; \
rm rootfs/etc/resolv.conf
# vim:set ft=dockerfile: