#!/bin/bash set -ex usage() { cat << EOF Usage: $PROGNAME [OPTION] Creates a directory with a readonly root on squashfs, a dm_verity file and an EFI executable --help Display this help --pkglist FILE The packages to install read from FILE (default: pkglist.txt) --excludelist FILE The packages to install read from FILE (default: excludelist.txt) --releasever NUM Used Fedora release version NUM (default: $VERSION_ID) --outdir DIR Creates DIR and puts all files in there (default: NAME-NUM-DATE) --name NAME The NAME of the product (default: FedoraBook) --logo FILE Uses the .bmp FILE to display as a splash screen (default: logo.bmp) --quirks LIST Source the list of quirks from the quikrs directory --gpgkey FILE Use FILE as the signing gpg key --reposd DIR Use DIR as the dnf repository directory --noupdate Do not install from Fedora Updates EOF } CURDIR=$(pwd) PROGNAME=${0##*/} BASEDIR=${0%/*} WITH_UPDATES=1 TEMP=$( getopt -o '' \ --long pkglist: \ --long excludelist: \ --long outdir: \ --long name: \ --long releasever: \ --long logo: \ --long quirks: \ --long gpgkey: \ --long reposd: \ --long noupdates \ -- "$@" ) if (( $? != 0 )); then usage >&2 exit 1 fi eval set -- "$TEMP" unset TEMP . /etc/os-release unset NAME declare -a QUIRKS while true; do case "$1" in '--pkglist') if [[ -f $2 ]]; then PKGLIST=$(<$2) else PKGLIST="$2" fi shift 2; continue ;; '--excludelist') if [[ -f $2 ]]; then EXCLUDELIST=$(<$2) else EXCLUDELIST="$2" fi shift 2; continue ;; '--outdir') OUTDIR="$2" shift 2; continue ;; '--name') NAME="$2" shift 2; continue ;; '--releasever') RELEASEVER="$2" shift 2; continue ;; '--logo') LOGO="$2" shift 2; continue ;; '--quirks') QUIRKS+=( $2 ) shift 2; continue ;; '--gpgkey') GPGKEY="$2" shift 2; continue ;; '--reposd') REPOSD="$2" shift 2; continue ;; '--noupdates') unset WITH_UPDATES shift 1; continue ;; '--') shift break ;; *) echo 'Internal error!' >&2 exit 1 ;; esac done [[ $EXCLUDELIST ]] || [[ -f excludelist.txt ]] && EXCLUDELIST=$(&2 exit 1 } readonly MY_TMPDIR="$(mktemp -p "$TMPDIR/" -d -t ${PROGNAME}.XXXXXX)" [ -d "$MY_TMPDIR" ] || { printf "%s\n" "${PROGNAME}: mktemp -p '$TMPDIR/' -d -t ${PROGNAME}.XXXXXX failed." >&2 exit 1 } # clean up after ourselves no matter how we die. trap ' ret=$?; mountpoint -q "$sysroot"/var/cache/dnf && umount "$sysroot"/var/cache/dnf for i in "$sysroot"/{dev,sys,proc,run}; do [[ -d "$i" ]] && mountpoint -q "$i" && umount "$i" done [[ $MY_TMPDIR ]] && rm -rf --one-file-system -- "$MY_TMPDIR" exit $ret; ' EXIT # clean up after ourselves no matter how we die. trap 'exit 1;' SIGINT if ! [[ -f "${BASEDIR}"/linuxx64.efi.stub ]]; then cp /lib/systemd/boot/efi/linuxx64.efi.stub "${BASEDIR}"/linuxx64.efi.stub fi readonly sysroot="${MY_TMPDIR}/sysroot" # We need to preserve old uid/gid mkdir -p "$sysroot"/etc for i in passwd shadow group gshadow subuid subgid; do [[ -e "${BASEDIR}/${NAME}/$i" ]] || continue cp -a "${BASEDIR}/${NAME}/$i" "$sysroot"/etc/"$i" done chown -R +0.+0 "$sysroot" mkdir -p "$sysroot"/{dev,proc,sys,run} mount --bind /proc "$sysroot/proc" #mount --bind /run "$sysroot/run" mount --bind /sys "$sysroot/sys" mount -t devtmpfs devtmpfs "$sysroot/dev" mkdir -p "$sysroot"/var/cache/dnf mount --bind /var/cache/dnf "$sysroot"/var/cache/dnf dnf -v --nogpgcheck \ --installroot "$sysroot"/ \ --releasever "$RELEASEVER" \ --exclude="$EXCLUDELIST" \ --setopt=keepcache=True \ --setopt=reposdir="$REPOSD" \ install -y \ dracut \ passwd \ rootfiles \ systemd \ systemd-udev \ kernel \ bash \ sudo \ strace \ xfsprogs \ pciutils \ microcode_ctl \ nss-altfiles \ nss_db \ keyutils \ make \ less \ polkit \ util-linux \ rng-tools \ openssl \ cryptsetup \ clevis \ clevis-luks \ clevis-systemd \ jose \ tpm2-tools \ coreutils \ libpwquality \ tpm2-tss \ ncurses-base \ dbus-broker \ tar \ gzip \ p11-kit \ efibootmgr \ jq \ gnupg2 \ $PKGLIST for i in passwd shadow group gshadow subuid subgid; do [[ -e "$sysroot"/etc/${i}.rpmnew ]] || continue while read line || [[ $line ]]; do IFS=: read user _ <<<$line grep -E -q "^$user:" "$sysroot"/etc/${i} && continue echo "$line" >> "$sysroot"/etc/${i} done <"$sysroot"/etc/${i}.rpmnew done find "$sysroot" -name '*.rpmnew' -print0 | xargs -0 rm -fv # We need to preserve old uid/gid mkdir -p ${BASEDIR}/${NAME} for i in passwd shadow group gshadow subuid subgid; do cp "$sysroot"/etc/"$i" ${BASEDIR}/${NAME} done cp "$CURDIR/clonedisk.sh" "$sysroot"/usr/bin/clonedisk cp "$CURDIR/update.sh" "$sysroot"/usr/bin/update cp "$CURDIR/update.sh" "$sysroot"/usr/bin/update mkdir -p "$sysroot"/etc/pki/${NAME} cp "${CURDIR}/${GPGKEY}" "$sysroot"/etc/pki/${NAME}/GPG-KEY rpm --root "$sysroot" -qa | sort > "$sysroot"/usr/rpm-list.txt mkdir -p "$sysroot"/overlay/efi cp "${BASEDIR}"/pre-pivot.sh "$sysroot"/pre-pivot.sh chmod 0755 "$sysroot"/pre-pivot.sh KVER=$(cd "$sysroot"/lib/modules/; ls -1d ??* | tail -1) sed -ie 's#\(tpm2_[^ ]*\) #\1 -T device:${TPM2TOOLS_DEVICE_FILE[0]} #g' "$sysroot"/usr/bin/clevis-*-tpm2 #--------------- # rngd ln -fsnr "$sysroot"/usr/lib/systemd/system/rngd.service "$sysroot"/usr/lib/systemd/system/basic.target.wants/rngd.service chroot "$sysroot" \ dracut -N --kver $KVER --force \ --filesystems "squashfs vfat xfs" \ --add-drivers "=drivers/char/tpm" \ -m "bash systemd systemd-initrd modsign crypt dm kernel-modules qemu rootfs-block udev-rules dracut-systemd base fs-lib shutdown terminfo resume" \ --install /usr/lib/systemd/systemd-veritysetup \ --install /usr/lib/systemd/system-generators/systemd-veritysetup-generator \ --install "clonedisk wipefs sfdisk dd mkfs.xfs mkswap chroot mountpoint mkdir stat openssl" \ --install "clevis clevis-luks-bind jose clevis-encrypt-tpm2 clevis-decrypt clevis-luks-unlock clevis-decrypt-tpm2" \ --install "cryptsetup tail sort pwmake mktemp swapon" \ --install "tpm2_pcrextend tpm2_createprimary tpm2_pcrlist tpm2_createpolicy" \ --install "tpm2_create tpm2_load tpm2_unseal tpm2_takeownership" \ --include /pre-pivot.sh /lib/dracut/hooks/pre-pivot/pre-pivot.sh \ --include /overlay / \ --install /usr/lib/systemd/system/clevis-luks-askpass.path \ --install /usr/lib/systemd/system/clevis-luks-askpass.service \ --install /usr/libexec/clevis-luks-askpass \ --include /usr/share/cracklib/ /usr/share/cracklib/ \ --install /usr/lib64/libtss2-esys.so.0 \ --install /usr/lib64/libtss2-tcti-device.so.0 \ --install /sbin/rngd \ --install /usr/lib/systemd/system/basic.target.wants/rngd.service rm "$sysroot"/pre-pivot.sh umount "$sysroot"/var/cache/dnf mkdir -p "$sysroot"/usr/share/factory/data/{var/etc,home} ln -sfnr "$sysroot"/usr/share/factory/data/var "$sysroot"/usr/share/factory/var ln -sfnr "$sysroot"/usr/share/factory/data/home "$sysroot"/usr/share/factory/home chroot "$sysroot" update-ca-trust . "${BASEDIR}"/quirks/nss.sh for q in "${QUIRKS[@]}"; do . "${BASEDIR}"/quirks/"$q".sh done #--------------- # timesync ln -fsnr "$sysroot"/usr/lib/systemd/system/systemd-timesyncd.service "$sysroot"/usr/lib/systemd/system/sysinit.target.wants/systemd-timesyncd.service #--------------- # dbus-broker ln -fsnr "$sysroot"/usr/lib/systemd/system/dbus-broker.service "$sysroot"/etc/systemd/system/dbus.service #--------------- # ssh if [[ -d "$sysroot"/etc/ssh ]]; then mv "$sysroot"/etc/ssh "$sysroot"/usr/share/factory/var/etc/ssh ln -sfnr "$sysroot"/var/etc/ssh "$sysroot"/etc/ssh cat >> "$sysroot"/usr/lib/tmpfiles.d/ssh.conf <> "$sysroot"/etc/passwd echo 'tss:!!:15587::::::' >> "$sysroot"/etc/shadow echo 'tss:x:59:' >> "$sysroot"/etc/group echo 'tss:!::' >> "$sysroot"/etc/gshadow fi #--------------- # NetworkManager if [[ -d "$sysroot"/etc/NetworkManager ]]; then mv "$sysroot"/etc/NetworkManager "$sysroot"/usr/share/factory/var/etc/ ln -fsnr "$sysroot"/var/etc/NetworkManager "$sysroot"/etc/NetworkManager cat >> "$sysroot"/usr/lib/tmpfiles.d/NetworkManager.conf <> "$sysroot"/usr/lib/tmpfiles.d/libvirt.conf <> "$sysroot"/usr/lib/tmpfiles.d/resolv.conf #--------------- # hostname ln -sfrn "$sysroot"/var/hostname "$sysroot"/etc/hostname echo "FedoraBook" > "$sysroot"/usr/share/factory/var/hostname #--------------- # vconsole.conf ln -fsnr "$sysroot"/var/vconsole.conf "$sysroot"/etc/vconsole.conf echo -e 'FONT=latarcyrheb-sun16\nKEYMAP=us' > "$sysroot"/usr/share/factory/var/vconsole.conf #--------------- # locale.conf ln -fsnr "$sysroot"/var/locale.conf "$sysroot"/etc/locale.conf echo 'LANG=en_US.UTF-8' > "$sysroot"/usr/share/factory/var/locale.conf #--------------- # localtime mv "$sysroot"/etc/localtime "$sysroot"/usr/share/factory/var/localtime ln -fsnr "$sysroot"/var/localtime "$sysroot"/etc/localtime #--------------- # adjtime mv "$sysroot"/etc/adjtime "$sysroot"/usr/share/factory/var/adjtime ln -fsnr "$sysroot"/var/adjtime "$sysroot"/etc/adjtime sed -i -e 's#/etc/locale.conf#/var/locale.conf#g;s#/etc/vconsole.conf#/var/vconsole.conf#g' "$sysroot"/usr/lib/systemd/systemd-localed sed -i -e 's#/etc/adjtime#/var/adjtime#g;s#/etc/localtime#/var/localtime#g' \ "$sysroot"/usr/lib/systemd/systemd-timedated \ "$sysroot"/usr/lib/systemd/libsystemd-shared*.so sed -i -e 's#ReadWritePaths=/etc#ReadWritePaths=/var#g' "$sysroot"/lib/systemd/system/systemd-localed.service sed -i -e 's#ReadWritePaths=/etc#ReadWritePaths=/var#g' "$sysroot"/lib/systemd/system/systemd-timedated.service cat >> "$sysroot"/usr/lib/tmpfiles.d/00-basics.conf <> "$sysroot"/usr/lib/tmpfiles.d/X11.conf < "$sysroot"/etc/sysctl.d/inotify.conf <"$sysroot"/etc/fstab < /usr/lib/tmpfiles.d/var-quirk.conf; :' mv "$sysroot"/lib/tmpfiles.d-var.conf "$sysroot"/lib/tmpfiles.d/var.conf sed -i -e "s#VERSION_ID=.*#VERSION_ID=$VERSION_ID#" "$sysroot"/etc/os-release sed -i -e "s#NAME=.*#NAME=$NAME#" "$sysroot"/etc/os-release mv -v "$sysroot"/boot/*/*/initrd "$MY_TMPDIR"/ mv -v "$sysroot"/lib/modules/*/vmlinuz "$MY_TMPDIR"/linux rm -fr "$sysroot"/{boot,root} ln -sfnr "$sysroot"/data/root "$sysroot"/root mkdir -p "$sysroot"/usr/etc mv "$sysroot"/etc/yum.repos.d "$sysroot"/usr/etc/yum.repos.d mkdir "$sysroot"/efi rm -fr "$sysroot"/var/* rm -fr "$sysroot"/home/* rm -fr "$sysroot"/etc/selinux mkdir "$sysroot"/data for i in "$sysroot"/{dev,sys,proc,run}; do [[ -d "$i" ]] && mountpoint -q "$i" && umount "$i" done # ------------------------------------------------------------------------------ # sysroot mksquashfs "$MY_TMPDIR"/sysroot "$MY_TMPDIR"/root.squashfs.img \ -noDataCompression -noFragmentCompression -noXattrCompression -noInodeCompression # ------------------------------------------------------------------------------ # verity ROOT_HASH=$(veritysetup format "$MY_TMPDIR"/root.squashfs.img "$MY_TMPDIR"/root.verity.img |& tail -1 | { read a b c; echo $c; } ) echo "$ROOT_HASH" > "$MY_TMPDIR"/root-hash.txt ROOT_UUID=${ROOT_HASH:32:8}-${ROOT_HASH:40:4}-${ROOT_HASH:44:4}-${ROOT_HASH:48:4}-${ROOT_HASH:52:12} HASH_UUID=${ROOT_HASH:0:8}-${ROOT_HASH:8:4}-${ROOT_HASH:12:4}-${ROOT_HASH:16:4}-${ROOT_HASH:20:12} # ------------------------------------------------------------------------------ # make bootx64.efi echo -n "rd.shell=0 quiet video=efifb:nobgrt audit=0 selinux=0 roothash=$ROOT_HASH systemd.verity_root_data=PARTUUID=$ROOT_UUID systemd.verity_root_hash=PARTUUID=$HASH_UUID raid=noautodetect" > "$MY_TMPDIR"/options.txt echo -n "${NAME}-${VERSION_ID}" > "$MY_TMPDIR"/release.txt objcopy \ --add-section .release="$MY_TMPDIR"/release.txt --change-section-vma .release=0x20000 \ --add-section .cmdline="$MY_TMPDIR"/options.txt --change-section-vma .cmdline=0x30000 \ ${LOGO:+--add-section .splash="$LOGO" --change-section-vma .splash=0x40000} \ --add-section .linux="$MY_TMPDIR"/linux --change-section-vma .linux=0x2000000 \ --add-section .initrd="$MY_TMPDIR"/initrd --change-section-vma .initrd=0x3000000 \ "${BASEDIR}"/linuxx64.efi.stub "$MY_TMPDIR"/bootx64.efi mkdir -p "$OUTDIR" mv "$MY_TMPDIR"/root-hash.txt \ "$MY_TMPDIR"/bootx64.efi \ "$MY_TMPDIR"/root.squashfs.img \ "$MY_TMPDIR"/root.verity.img \ "$MY_TMPDIR"/release.txt \ "$MY_TMPDIR"/options.txt \ "$MY_TMPDIR"/linux \ "$MY_TMPDIR"/initrd \ "$OUTDIR" chown -R "$USER" "$OUTDIR" cat > "${OUTDIR%/*}/${NAME}-latest.json" <