treehouses/builder

View on GitHub
builder

Summary

Maintainability
Test Coverage
#!/bin/bash
# Download Raspbian Image, remove first-boot stuff, add repos and install packages.
 
# Raspbian
architecture="$2"
case "$architecture" in
"armhf" | "")
RASPBIAN_TORRENT_URL=https://downloads.raspberrypi.org/raspios_armhf/images/raspios_armhf-2021-05-28/2021-05-07-raspios-buster-armhf.zip.torrent
RASPBIAN_SHA256=b6c04b34d231f522278fc822d913fed3828d0849e1e7d786db72f52c28036c62
;;
"arm64")
RASPBIAN_TORRENT_URL=downloads.raspberrypi.org/raspios_arm64/images/raspios_arm64-2021-05-28/2021-05-07-raspios-buster-arm64.zip.torrent
RASPBIAN_SHA256=f882c4c7202074277938b04fa770411c4b8a12f49e9ad2c2d70966e168b7bcb4
;;
esac
 
RASPBIAN_IMAGE_FILE=$(basename $RASPBIAN_TORRENT_URL | sed -e "s/.zip.torrent/.img/g")
 
EXTRA_IMAGE_SIZE=1850MB
 
MINIMAL_SPACE_LEFT appears unused. Verify it or export it.
MINIMAL_SPACE_LEFT=111111
 
source lib.sh
 
missing_deps=()
for prog in kpartx wget gpg parted qemu-arm-static aria2c jq curl; do
if ! type $prog &>/dev/null ; then
missing_deps+=( "$prog" )
fi
done
if (( ${#missing_deps[@]} > 0 )) ; then
die "Missing required programs: ${missing_deps[*]}
On Debian/Ubuntu try 'sudo apt install kpartx qemu-user-static parted wget curl jq aria2'"
 
fi
 
function _umount {
for dir in "$@" ; do
if grep -q "$dir" /proc/self/mounts ; then
if ! umount -f "$dir" ; then
# shellcheck disable=SC2046,SC2086
die "Could not umount $dir, check running procs:$NL$(lsof 2>/dev/null | grep $(readlink -f $dir))"
fi
fi
done
}
 
function _get_image {
echo "Fetching $RASPBIAN_TORRENT_URL"
mkdir -p images
if [ ! -f "$RASPBIAN_TORRENT" ]; then
wget "$RASPBIAN_TORRENT_URL" -O "$RASPBIAN_TORRENT" || die "Download of $RASPBIAN_TORRENT failed"
fi
aria2c --enable-dht=true --bt-enable-lpd=true --continue "$RASPBIAN_TORRENT" -d images --seed-time 0
echo -n "Checksum of "
sha256sum --strict --check - <<<"$RASPBIAN_SHA256 *$IMAGE_ZIP" || die "Download checksum validation failed, please check http://www.raspberrypi.org/downloads"
}
 
function _decompress_image {
unzip -o "$IMAGE_ZIP" -d images || die "Could not unzip $IMAGE_ZIP"
}
 
function _disable_daemons {
# Prevent services from being started inside the chroot.
POLICY_RC_D=mnt/img_root/usr/sbin/policy-rc.d
echo "#!/bin/sh" >> $POLICY_RC_D
echo "exit 101" >> $POLICY_RC_D
chmod +x $POLICY_RC_D
}
 
function _enable_daemons {
POLICY_RC_D=mnt/img_root/usr/sbin/policy-rc.d
rm -f $POLICY_RC_D
}
 
function _disable_ld_preload {
cfg=mnt/img_root/etc/ld.so.preload
 
if grep -q '^[^#]' $cfg; then
sed -i -e 's/^/#/' $cfg || die "Could not disable ld.so.preload"
fi
}
 
function _enable_ld_preload {
cfg=mnt/img_root/etc/ld.so.preload
 
if grep -q '^#' $cfg; then
sed -i -e 's/^#//' $cfg || die "Could not enable ld.so.preload"
fi
}
 
function _resize_image {
RESIZE_IMAGE_PATH=images/$RASPBIAN_IMAGE_FILE
if [[ -L "images" ]];
then
rsync -Pav "images/$RASPBIAN_IMAGE_FILE" .
RESIZE_IMAGE_PATH=$RASPBIAN_IMAGE_FILE
fi
 
start_sector=$(fdisk -l "$RESIZE_IMAGE_PATH" | awk -F" " '{ print $2 }' | sed '/^$/d' | sed -e '$!d')
LOOP_BASE=$(losetup -a | grep -c 'loop') #formerly loop0, loop1, loop2
echo "LOOP BASE: $LOOP_BASE"
LOOP_ONE=$(( LOOP_BASE + 1 ))
echo "LOOP ONE: $LOOP_ONE"
LOOP_TWO=$(( LOOP_BASE + 2 ))
echo "LOOP TWO: $LOOP_TWO"
truncate -s +$EXTRA_IMAGE_SIZE "$RESIZE_IMAGE_PATH"
losetup "/dev/loop$LOOP_ONE" "$RESIZE_IMAGE_PATH"
fdisk "/dev/loop$LOOP_ONE" <<EOF
p
d
2
n
p
2
$start_sector
 
p
w
EOF
losetup -d "/dev/loop$LOOP_ONE"
losetup -o $((start_sector*512)) "/dev/loop$LOOP_TWO" "$RESIZE_IMAGE_PATH"
e2fsck -f "/dev/loop$LOOP_TWO"
resize2fs -f "/dev/loop$LOOP_TWO"
losetup -d "/dev/loop$LOOP_TWO"
if [[ -L "images" ]];
then
rsync -Pav "$RASPBIAN_IMAGE_FILE" images/
rm "$RASPBIAN_IMAGE_FILE"
fi
}
 
function _open_image {
echo "Stupid Snaps"
losetup -a | grep 'loop'
echo "Loop-back mounting" "images/$RASPBIAN_IMAGE_FILE"
# shellcheck disable=SC2086
kpartx="$(kpartx -sav images/$RASPBIAN_IMAGE_FILE)" || die "Could not setup loop-back access to $RASPBIAN_IMAGE_FILE:$NL$kpartx"
# shellcheck disable=SC2162
read -d '' img_boot_dev img_root_dev <<<"$(grep -o 'loop.p.' <<<"$kpartx")"
test "$img_boot_dev" -a "$img_root_dev" || die "Could not extract boot and root loop device from kpartx output:$NL$kpartx"
img_boot_dev=/dev/mapper/$img_boot_dev
img_root_dev=/dev/mapper/$img_root_dev
mkdir -p mnt/img_root
mount -t ext4 "$img_root_dev" mnt/img_root || die "Could not mount $img_root_dev mnt/img_root"
mkdir -p mnt/img_root/boot || die "Could not mkdir mnt/img_root/boot"
mount -t vfat "$img_boot_dev" mnt/img_root/boot || die "Could not mount $img_boot_dev mnt/img_root/boot"
echo "Raspbian Image Details:"
df -h mnt/img_root/boot mnt/img_root | sed -e "s#$(pwd)/##"
}
 
function _close_image {
_umount mnt/img_root/var/cache/apt/archives \
mnt/img_root/{proc,sys,run,dev/pts} \
mnt/sd_root/bo?t mnt/img_root/boot \
mnt/sd_ro?t mnt/img_root
kpartx -d "images/$RASPBIAN_IMAGE_FILE" >/dev/null
}
 
function _prepare_chroot {
_disable_ld_preload
 
cp -a "$(type -p qemu-arm-static)" mnt/img_root/usr/bin/ || die "Could not copy qemu-arm-static"
_chroot date &>/dev/null || die "Could not chroot date"
 
mount -t devpts devpts -o noexec,nosuid,gid=5,mode=620 mnt/img_root/dev/pts || die "Could not mount /dev/pts"
mount -t proc proc mnt/img_root/proc || die "Could not mount /proc"
mount -t sysfs sys mnt/img_root/sys || die "Could not mount /sys"
mount -t tmpfs -o mode=1777 none mnt/img_root/run || "Could not mount /run"
 
mkdir -p apt_cache
mount --bind apt_cache mnt/img_root/var/cache/apt/archives
}
 
function _cleanup_chroot {
_umount mnt/img_root/var/cache/apt/archives \
mnt/img_root/{proc,sys,run,dev/pts}
_enable_daemons
_enable_ld_preload
}
 
function _check_space_left {
space_left=$(df | grep "dev/mapper/loop$LOOP_BASE\p2" | awk '{printf $4}')
echo "Space left: ${space_left}K"
}
 
function _count_authorized_keys_lines {
authorized_keys_lines=$(wc -l < mnt/img_root/root/.ssh/authorized_keys)
echo "There are ${authorized_keys_lines} line(s) in /root/.ssh/authorized_keys"
}
 
function _modify_image {
echo "Modifying Image"
 
_prepare_chroot
_disable_daemons
 
run-parts --arg="$architecture" --exit-on-error -v --regex '[a-zA-Z.-_]*' scripts.d ||\
die "Image modification scripts failed"
 
_enable_daemons
_check_space_left
_count_authorized_keys_lines
_cleanup_chroot
}
 
function _usage {
echo "
Usage: $0 <--chroot|--noninteractive>
 
Download Raspbian Image, remove first-boot stuff, add repos and install packages.
 
Open interactive Shell in chroot or write result to SD Card
 
License: GNU General Public License, see http://www.gnu.org/copyleft/gpl.html for full text
"
}
 
function _shell {
_prepare_chroot
chroot mnt/img_root bash -i
_cleanup_chroot
}
 
export LANG="C" LANGUAGE="C" LC_ALL="C.UTF-8"
shopt -s nullglob
 
if [[ $# -eq 0 || "$*" == *-h* ]] ; then
_usage
exit 1
fi
 
if [[ "$USER" != root && $(id -u) != "0" ]] ; then
# restart as root
echo "Switching over to run as root"
exec sudo "$(readlink -f "$0")" "$@"
echo "Need sudo permission to run as root!"
exit 1
fi
 
if grep -wq "$(readlink -f mnt)" /proc/self/mounts; then
die "mnt/ is already mounted!"
fi
 
rm -Rf --one-file-system mnt temp
function exittrap {
set +u +e
_close_image
echo "Script execution time: $SECONDS seconds"
}
trap exittrap 0
trap exittrap ERR
 
function _print_tag {
tag=$(git tag --sort=-creatordate | sed -n '1p')
echo
echo "latest tag is '$tag'"
echo
}
 
_print_tag
 
RASPBIAN_TORRENT=images/$(basename $RASPBIAN_TORRENT_URL)
echo "$RASPBIAN_TORRENT"
IMAGE_ZIP=${RASPBIAN_TORRENT%.torrent}
echo "$IMAGE_ZIP"
IMAGE=${IMAGE_ZIP%.zip}.img
echo "$IMAGE"
 
if [ ! -e "$IMAGE_ZIP" ]; then
_get_image
fi
 
_decompress_image
_resize_image
_open_image
 
if [[ "$1" == "--chroot" ]] ; then
_modify_image
echo "Starting interactive Shell in image chroot"
_shell
elif [[ "$1" == "--noninteractive" ]] ; then
_modify_image
elif [[ "$1" == "--shell" ]]; then
_shell
else
die "Usage error. Try $0 --help"
fi
 
#if [[ $space_left -lt $MINIMAL_SPACE_LEFT ]]; then
# echo "Not enough space left."
# exit 1
#fi
 
if [[ $authorized_keys_lines -le 20 ]]; then
echo "/root/.ssh/authorized_keys has 20 line or less."
exit 1
fi
 
# vim:autoindent:tabstop=2:shiftwidth=2:expandtab:softtabstop=2: