diff --git a/meta-citadel/classes/citadel-image.bbclass b/meta-citadel/classes/citadel-image.bbclass index 193fa97..3c90682 100644 --- a/meta-citadel/classes/citadel-image.bbclass +++ b/meta-citadel/classes/citadel-image.bbclass @@ -1,8 +1,6 @@ - -DEPENDS:append = " citadel-tools-native mtools-native cryptsetup-native coreutils-native" +DEPENDS:append = " make-ext4fs-native citadel-tools-native mtools-native cryptsetup-native coreutils-native" # Block size must be 4096 or dm-verity won't work -EXTRA_IMAGECMD:ext4 = "-i 4096 -b 4096" IMAGE_FSTYPES = "ext4" IMAGE_OVERHEAD_FACTOR = "1.2" @@ -16,7 +14,7 @@ do_citadel_mkimage() { image-type = "${CITADEL_IMAGE_TYPE}" channel = "${CITADEL_IMAGE_CHANNEL}" version = ${CITADEL_IMAGE_VERSION} -timestamp = "${DATETIME}" +timestamp = "${SOURCE_DATE_EPOCH}" source = "${IMGDEPLOYDIR}/${IMAGE_LINK_NAME}.ext4" compress = ${CITADEL_IMAGE_COMPRESS} EOF diff --git a/meta-citadel/classes/make_repro_ext4fs.bbclass b/meta-citadel/classes/make_repro_ext4fs.bbclass new file mode 100644 index 0000000..3de0bce --- /dev/null +++ b/meta-citadel/classes/make_repro_ext4fs.bbclass @@ -0,0 +1,51 @@ +inherit image_types + +python set_image_size () { + import math + + print("LOCAL set_image_size") + blocksize = 50000 + rootfs_size = get_rootfs_size(d) + print("LOCAL rootfs_size") + + rootfs_size = math.ceil(rootfs_size / blocksize) * blocksize + print("LOCAL rootfs_size") + + d.setVar('ROOTFS_SIZE', str(rootfs_size)) + d.setVarFlag('ROOTFS_SIZE', 'export', '1') +} + +make_repro_ext4fs() { + fstype=ext4 + extra_imagecmd="" + + if [ $# -gt 1 ]; then + shift + extra_imagecmd=$@ + fi + + # If generating an empty image the size of the sparse block should be large + # enough to allocate an ext4 filesystem using 4096 bytes per inode, this is + # about 60K, so dd needs a minimum count of 60, with bs=1024 (bytes per IO) + eval local COUNT=\"0\" + eval local MIN_COUNT=\"60\" + if [ $ROOTFS_SIZE -lt $MIN_COUNT ]; then + eval COUNT=\"$MIN_COUNT\" + fi + + # Create a sparse image block + bbdebug 1 Executing "dd if=/dev/zero of=${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype count=1 bs=1024" + + bbdebug 1 "ROOTFS_SIZE: `${ROOTFS_SIZE}`" + bbdebug 1 Executing "make_ext4fs -vl ${ROOTFS_SIZE}k -T "1712775988" ${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype ${IMAGE_ROOTFS}" + + make_ext4fs -vl ${ROOTFS_SIZE}k -T "1712775988" ${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype ${IMAGE_ROOTFS} + + # Error codes 0-3 indicate successfull operation of fsck (no errors or errors corrected) + fsck.ext4 -pvD ${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype || [ $? -le 3 ] + # adding f makes it non-reproducible + +# delete the lost+found dir's contents +# mount ${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype /tmp/image/ +# find "-iname" /tmp/image/lost+found -type d -exec rm -r "{}" \; +} diff --git a/meta-citadel/conf/templates/default/local.conf.sample b/meta-citadel/conf/templates/default/local.conf.sample index 77bcb3b..84b0b81 100644 --- a/meta-citadel/conf/templates/default/local.conf.sample +++ b/meta-citadel/conf/templates/default/local.conf.sample @@ -38,6 +38,16 @@ #MACHINE ??= "qemux86-64" MACHINE ?= "intel-corei7-64" +# +# Binary Reproducibility +# +BUILD_REPRODUCIBLE_BINARIES = "1" +export PYTHONHASHSEED = "0" +export PERL_HASH_SEED = "0" +export TZ = 'UTC' +export SOURCE_DATE_EPOCH ??= "1718285985" +REPRODUCIBLE_TIMESTAMP_ROOTFS ??= "1712775988" + DEFAULT_TIMEZONE = "America/New_York" DEPLOY_DIR_IMAGE = "${TOPDIR}/images" diff --git a/meta-citadel/recipes-citadel/images/base-realmfs-image.bb b/meta-citadel/recipes-citadel/images/base-realmfs-image.bb index 437bdb8..16a1685 100644 --- a/meta-citadel/recipes-citadel/images/base-realmfs-image.bb +++ b/meta-citadel/recipes-citadel/images/base-realmfs-image.bb @@ -15,7 +15,7 @@ do_realmfs_mkimage() { image-type = "realmfs" channel = "${CITADEL_IMAGE_CHANNEL}" version = 1 -timestamp = "${DATETIME}" +timestamp = "${SOURCE_DATE_EPOCH}" source = "${REALMFS_DIR}/citadel-realmfs.ext4" realmfs-name = "base" compress = true diff --git a/meta-citadel/recipes-citadel/images/citadel-extra-image.bb b/meta-citadel/recipes-citadel/images/citadel-extra-image.bb index 9456c9e..7281865 100644 --- a/meta-citadel/recipes-citadel/images/citadel-extra-image.bb +++ b/meta-citadel/recipes-citadel/images/citadel-extra-image.bb @@ -18,7 +18,8 @@ CITADEL_IMAGE_VERSION = "${CITADEL_IMAGE_VERSION_extra}" CITADEL_IMAGE_TYPE = "extra" require citadel-image.inc -inherit citadel-image +inherit citadel-image make_repro_ext4fs +IMAGE_CMD:ext4 = "make_repro_ext4fs" ROOTFS_POSTPROCESS_COMMAND += "write_manifest_file; " diff --git a/meta-citadel/recipes-citadel/images/citadel-installer-image.bb b/meta-citadel/recipes-citadel/images/citadel-installer-image.bb index 656ba5e..98725e2 100644 --- a/meta-citadel/recipes-citadel/images/citadel-installer-image.bb +++ b/meta-citadel/recipes-citadel/images/citadel-installer-image.bb @@ -139,8 +139,51 @@ write_boot_image() { fi bbdebug 1 Creating ${IMAGE_SIZE} block msdos image at ${IMAGE_PATH} - mkdosfs -n boot -C ${IMAGE_PATH} ${IMAGE_SIZE} - mcopy -i ${IMAGE_PATH} -s ${IMAGE_ROOTFS}/* ::/ + mkdosfs --invariant -i 2e24ec82 -n BOOT -C ${IMAGE_PATH} ${IMAGE_SIZE} + + ############################################################################### + echo "Running mmd and mcopy per file and dir to place files in the final fat32 image reproducibly" + + INDIR=${IMAGE_ROOTFS} + + # mmd is silly and requires this line to know where the image file is + echo "drive x: file=\"${IMAGE_PATH}\"" > ~/.mtoolsrc + echo ${OUTDIR} + + for file in $(ls ${INDIR}/ | sort) + do + if [ -d ${INDIR}/${file} ] ; then + echo "lvl 1 mmd ${file}" + mmd x:/${file} + + for file1 in $(ls ${INDIR}/${file}/ | sort) + do + if [ -d ${INDIR}/${file}/${file1} ] ; then + echo "lvl 2 mmd ${file}/${file1}" + mmd x:/${file}/${file1} + + for file2 in $(ls ${INDIR}/${file}/${file1}/ | sort) + do + echo "lvl 2 mmd ${file}/${file1}/${file2}" + if [ -d ${INDIR}/${file}/${file1}/${file2} ] ; then + mmd x:/${file}/${file1}/${file2} + + else + echo "lvl 4 mcopy ${INDIR}/${file}/${file1}/${file2}" + mcopy -i ${IMAGE_PATH} -vs ${INDIR}/${file}/${file1}/${file2} ::/${file}/${file1}/ + fi + done + else + echo "lvl 2 mcopy ${INDIR}/${file}/${file1}" + mcopy -i ${IMAGE_PATH} -vs ${INDIR}/${file}/${file1} ::/${file}/ + fi + done + else + echo "lvl 2 mcopy ${INDIR}/${file}" + mcopy -i ${IMAGE_PATH} -vs ${INDIR}/${file} ::/ + fi + done + ############################################################################### syslinux --directory syslinux --install ${IMAGE_PATH} } @@ -160,18 +203,31 @@ write_installer_image() { bbdebug 1 Creating ${TOTAL_IMAGE_BLOCKS} block empty image file at ${INSTALLER_IMAGE} truncate -s ${TOTAL_IMAGE_BLOCKS}K ${INSTALLER_IMAGE} parted -s ${INSTALLER_IMAGE} mklabel msdos + # now set disk Identifier manually to make reproducible + # thank you mook765 @ https://askubuntu.com/questions/1250224/how-to-change-partuuid + bash -c "sed -e 's/\s*\([\+0-9a-zA-Z]*\).*/\1/' <> /home/user/.bashrc diff --git a/realmfs-builder/realmfs-modules/write-apt-sources b/realmfs-builder/realmfs-modules/write-apt-sources index f48c803..deb19b5 100644 --- a/realmfs-builder/realmfs-modules/write-apt-sources +++ b/realmfs-builder/realmfs-modules/write-apt-sources @@ -3,6 +3,5 @@ info "Writing /etc/apt/sources.list" { echo "deb ${DEBIAN_MIRROR} ${DEBIAN_RELEASE} main contrib non-free" echo "deb ${DEBIAN_MIRROR}-security ${DEBIAN_RELEASE}-security main contrib non-free" - echo "deb ${DEBIAN_MIRROR} unstable main" } > /etc/apt/sources.list diff --git a/realmfs-builder/stage-one.sh b/realmfs-builder/stage-one.sh index d993130..b5ece7f 100755 --- a/realmfs-builder/stage-one.sh +++ b/realmfs-builder/stage-one.sh @@ -29,22 +29,43 @@ setup_rootfs() { } run_debootstrap() { + #[[ -f ${CACHE_DIR}/lock ]] && rm -f ${CACHE_DIR}/lock + #mkdir --parents ${CACHE_DIR} ${ROOTFS}/var/cache/apt/archives - [[ -f ${CACHE_DIR}/lock ]] && rm -f ${CACHE_DIR}/lock - mkdir --parents ${CACHE_DIR} ${ROOTFS}/var/cache/apt/archives + #info "Bind mounting ${CACHE_DIR} to ${ROOTFS}/var/cache/apt/archives" + #mount --bind ${CACHE_DIR} ${ROOTFS}/var/cache/apt/archives - info "Bind mounting ${CACHE_DIR} to ${ROOTFS}/var/cache/apt/archives" - mount --bind ${CACHE_DIR} ${ROOTFS}/var/cache/apt/archives + info "Launching mmdebstrap" - info "Launching debootstrap" + export SOURCE_DATE_EPOCH="1718285985" - debootstrap --verbose --merged-usr --variant=minbase \ - --include=systemd-sysv,locales \ - ${DEBIAN_RELEASE} ${ROOTFS} ${DEBIAN_MIRROR} + mmdebstrap --variant=minbase \ + --include=systemd-sysv,locales,ca-certificates \ + ${DEBIAN_RELEASE} ${ROOTFS} ${DEBIAN_MIRROR} +} + +make_reproducible() { + #umount ${ROOTFS}/var/cache/apt/archives + + rm -rdf ${ROOTFS}/var/cache/* + rm ${ROOTFS}/var/log/apt/term.log + rm ${ROOTFS}/var/log/apt/history.log + rm ${ROOTFS}/var/log/bootstrap.log || true + rm ${ROOTFS}/var/log/fontconfig.log || true + rm ${ROOTFS}/var/log/dpkg.log + rm ${ROOTFS}/var/log/alternatives.log + rm ${ROOTFS}/var/log/eipp.log.xz || true + rm -rdf ${ROOTFS}/var/lib/apt/lists/* + awk -i inplace -F":" '{OFS=FS}{ $3="1" ; print }' ${ROOTFS}/etc/shadow # do not record date of last password change + + echo "bf58db8bc11448788138633a01a06cdd" > ${ROOTFS}/etc/machine-id + echo "bf58db8bc11448788138633a01a06cdd" > ${ROOTFS}/var/lib/dbus/machine-id + + echo -e "# Generated during realmfs build\nnameserver 192.168.4.1" > ${ROOTFS}/etc/resolv.conf + echo -e "# File generated during realmfs build\nLC_COLLATE=C\nLANG=en_US.UTF-8" > ${ROOTFS}/etc/default/locale } setup_chroot() { - mount chproc ${ROOTFS}/proc -t proc mount chsys ${ROOTFS}/sys -t sysfs mount chtmp ${ROOTFS}/tmp -t tmpfs @@ -60,10 +81,11 @@ setup_chroot() { } cleanup_chroot() { + make_reproducible + umount ${ROOTFS}/proc umount ${ROOTFS}/sys umount ${ROOTFS}/tmp - umount ${ROOTFS}/var/cache/apt/archives # Remove cache files in case we are creating a tarball for distribution rm -f ${ROOTFS}/var/cache/apt/pkgcache.bin @@ -71,7 +93,6 @@ cleanup_chroot() { } run_chroot_stage() { - setup_chroot # @@ -103,15 +124,27 @@ generate_tarball() { echo } +build_make_ext4fs() { + cd ${WORKDIR} + if [ ! -d "make_ext4fs" ]; then + git clone https://git.subgraph.com/isa/make_ext4fs.git + fi + cd make_ext4fs + git checkout 5c201be7d72aff735da27e17c29852e0cefe3e52 + make + cd ../.. +} + generate_image() { # BLOCKS=$(du -ks ${ROOTFS} | cut -f1) # BLOCKS=$(expr ${BLOCKS} \* 12 / 10) # SIZE=$(expr ${BLOCKS} \* 1024) # echo "Size is ${SIZE}" + build_make_ext4fs BLOCKS=$(expr 440 \* 1024) # allow online resize up to 32G dd if=/dev/zero of=${WORKDIR}/citadel-realmfs.ext4 seek=${BLOCKS} count=0 bs=4096 - mkfs.ext4 -d ${ROOTFS} -i 4096 -b 4096 -F ${WORKDIR}/citadel-realmfs.ext4 ${BLOCKS} || exit 1 + ${WORKDIR}/make_ext4fs/make_ext4fs -l 2G -T "1712775988" -b 4096 ${WORKDIR}/citadel-realmfs.ext4 ${ROOTFS} || exit 1 } usage() { @@ -156,7 +189,6 @@ try_config() { } WORKDIR="$(pwd)/realmfs" -BUILDFILE="" DO_TAR=0 DO_XZ=0 @@ -223,7 +255,7 @@ if [ "$EUID" -ne 0 ]; then exit 1 fi -if [[ -z ${BUILDFILE} ]]; then +if [[ -z ${BUILDFILE-} ]]; then BUILDFILE=$(try_config "${PWD}/build.conf" || try_config "${REALMFS_BUILDER_BASE}/basic-image.conf") || fatal "Could not find a configuration file to use" fi @@ -245,7 +277,6 @@ run_debootstrap run_chroot_stage - info "rootfs build is completed:" info " $(du -sh ${ROOTFS})" diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile index c99312a..4097bad 100644 --- a/scripts/docker/Dockerfile +++ b/scripts/docker/Dockerfile @@ -30,7 +30,12 @@ RUN apt update && apt install -y gawk \ file \ liblz4-tool \ zstd \ - xwayland + xwayland \ + mmdebstrap \ + apt-utils \ + usrmerge \ + faketime \ + diffoscope # python RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen @@ -38,6 +43,7 @@ RUN locale-gen RUN update-locale LANG=en_US.UTF-8 ENV LC_ALL en_US.UTF-8 ENV LC_CTYPE en_US.UTF-8 +ENV LC_COLLATE en_US.UTF-8 RUN useradd -ms /bin/bash builder RUN echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers