| #!/bin/bash |
| # |
| # Copyright 2021 The Cobalt Authors. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the 'License'); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an 'AS IS' BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # This script downloads and customizes a Raspbian image for running |
| # Cobalt. Qemu is used for running on-target customization steps. |
| # Please see README for full description |
| |
| # Image versions to use |
| BASEDIR=raspbian_lite-2020-02-14 |
| BASENAME=2020-02-13-raspbian-buster-lite |
| |
| # Image and script sources |
| IMAGE_URL=http://downloads.raspberrypi.org/raspbian_lite/images |
| SHRINK_SCRIPT="https://raw.githubusercontent.com/aoakley/cotswoldjam/\ |
| master/raspbian-shrink/raspbian-shrink" |
| |
| # Set up locations for temporary files |
| STAGING_DIR=/tmp/pi2_image |
| IMG_FILE=${STAGING_DIR}/${BASENAME}.img |
| SHRUNK_IMG_FILE=${STAGING_DIR}/${BASENAME}_shrunk_$(date +"%Y%m%d").img |
| BOOT_MOUNT=${STAGING_DIR}/boot |
| ROOT_MOUNT=${STAGING_DIR}/rootfs |
| SSH_KEY_FILE=${STAGING_DIR}/pi_key |
| QEMU_ADDRESS=127.0.0.1 |
| |
| # Reuse some command args |
| RUN_ON_PI="ssh -i ${SSH_KEY_FILE} -o StrictHostKeyChecking=no -p 8022 pi@${QEMU_ADDRESS} sudo" |
| WGET="wget --show-progress -nc" |
| |
| # Check expected tools are available |
| function tools_check() { |
| if ! command -v qemu-system-arm --version &> /dev/null ; then |
| echo Could not find QEMU, see README for instructions |
| return |
| fi |
| if ! command -v dcfldd --version &> /dev/null ; then |
| echo Could not find dcfldd, see README for instructions |
| fi |
| } |
| |
| function fetch_image() { |
| mkdir -p ${STAGING_DIR} |
| ${WGET} ${IMAGE_URL}/${BASEDIR}/${BASENAME}.zip \ |
| -O ${STAGING_DIR}/${BASENAME}.zip |
| if [[ ! -f ${IMG_FILE} ]]; then |
| unzip ${STAGING_DIR}/${BASENAME}.zip -d ${STAGING_DIR} |
| fi |
| } |
| |
| # Mount the image file boot partition |
| function mount_boot() { |
| BOOT_LOCATION=$(fdisk -l ${IMG_FILE}| egrep img1| tr -s ' '| cut -f 2 -d " ") |
| MOUNT_OFFSET=$(bc <<< "$BOOT_LOCATION * 512") |
| mkdir -p ${BOOT_MOUNT} |
| sudo mount -v -o offset=${MOUNT_OFFSET} ${IMG_FILE} ${BOOT_MOUNT} |
| } |
| |
| # Mount the image file root partition |
| function mount_root() { |
| BOOT_LOCATION=$(fdisk -l ${IMG_FILE}| egrep img2| tr -s ' '| cut -f 2 -d " ") |
| MOUNT_OFFSET=$(bc <<< "$BOOT_LOCATION * 512") |
| mkdir -p ${ROOT_MOUNT} |
| sudo mount -v -o offset=${MOUNT_OFFSET} -t ext4 ${IMG_FILE} ${ROOT_MOUNT} |
| } |
| |
| function unmount_root() { |
| sudo umount ${ROOT_MOUNT} |
| } |
| function unmount_boot() { |
| sudo umount ${BOOT_MOUNT} |
| } |
| |
| function modify_boot() { |
| # Set GPU/main memory split |
| sudo sh -c "echo gpu_mem=512 >> ${BOOT_MOUNT}/config.txt" |
| # Ensure 1st boot image resize runs |
| sudo touch ${BOOT_MOUNT}/1stboot.txt |
| # Drop auto-resize script in place, crontab entry will be added later |
| sudo cp -v auto-resize-partition.sh ${BOOT_MOUNT}/ |
| sudo cp -v crontab.txt ${BOOT_MOUNT}/ |
| sudo chmod +x ${BOOT_MOUNT}/auto-resize-partition.sh |
| } |
| |
| function modify_root() { |
| # Enable SSH by default |
| sudo rm ${ROOT_MOUNT}/etc/{rc2.d,rc3.d,rc4.d,rc5.d}/K01ssh |
| for i in 2 3 4 5 ; do |
| sudo ln -s ../init.d/ssh ${ROOT_MOUNT}/etc/rc${i}.d/S02ssh |
| done |
| sudo ln -s /lib/systemd/system/ssh.service \ |
| ${ROOT_MOUNT}/etc/systemd/system/multi-user.target.wants/ssh.service |
| sudo ln -s /lib/systemd/system/ssh.service \ |
| ${ROOT_MOUNT}/etc/systemd/system/sshd.service |
| } |
| |
| function get_kernel() { |
| # Get matching kernel and DTB files from boot partition |
| cp ${BOOT_MOUNT}/kernel7.img ${STAGING_DIR} |
| cp ${BOOT_MOUNT}/bcm2709-rpi-2-b.dtb ${STAGING_DIR} |
| } |
| |
| function run_qemu() { |
| tools_check |
| # Ensure image is in usable size for Qemu |
| qemu-img resize ${IMG_FILE} 4G |
| # Boot qemu in background |
| qemu-system-arm $@ \ |
| -machine raspi2 \ |
| -drive file=${IMG_FILE},format=raw,if=sd \ |
| -dtb ${STAGING_DIR}/bcm2709-rpi-2-b.dtb \ |
| -kernel ${STAGING_DIR}/kernel7.img \ |
| -append 'rw earlycon=pl011,0x3f201000 console=ttyAMA0 \ |
| loglevel=8 root=/dev/mmcblk0p2 fsck.repair=yes \ |
| net.ifnames=0 rootwait memtest=1 \ |
| dwc_otg.fiq_fsm_enable=0' \ |
| -m 1G -smp 4 \ |
| -no-reboot \ |
| -netdev user,id=net0,hostfwd=tcp::8022-:22 \ |
| -usb -device usb-kbd -device usb-tablet \ |
| -device usb-net,netdev=net0 |
| } |
| |
| function run_qemu_backgrounded() { |
| run_qemu -daemonize |
| } |
| |
| # Useful for local image testing |
| function run_qemu_console() { |
| run_qemu -serial stdio |
| } |
| |
| function kill_qemu() { |
| pkill -9 qemu-system-arm |
| } |
| |
| function make_ssh_key() { |
| if [[ ! -f ${SSH_KEY_FILE} ]]; then |
| ssh-keygen -q -N "" -b 1024 -f ${SSH_KEY_FILE} \ |
| -C "cobalt-dev@googlegroups.com" |
| fi |
| } |
| |
| function deploy_key() { |
| echo Copying SSH key to target, please use password \'raspberry\' at \ |
| the login prompt. |
| # Remove previous host key |
| ssh-keygen -R ${QEMU_ADDRESS} |
| # Accept a new one |
| ssh-keyscan -p 8022 ${QEMU_ADDRESS} >> ~/.ssh/known_hosts |
| ssh-copy-id -i ${SSH_KEY_FILE} -p 8022 -o StrictHostKeyChecking=no pi@${QEMU_ADDRESS} |
| ${RUN_ON_PI} echo ssh connection works |
| } |
| |
| function wait_for_qemu() { |
| # Loop until SSH connection attempt responds with permission denied |
| while :; do |
| echo "waiting for SSH to be up" |
| cmd=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes \ |
| localhost -p 8022 echo up 2>&1) |
| OUTPUT=$? |
| if [ $OUTPUT -eq 255 ]; then |
| if [[ $cmd == *"Permission"* ]] ; then |
| echo "SSH seems up" |
| break |
| fi |
| fi |
| echo $cmd_out |
| sleep 2 |
| done |
| } |
| |
| function get_shrink_script() { |
| wget -nc ${SHRINK_SCRIPT} -O ${STAGING_DIR}/raspbian-shrink |
| chmod +x ${STAGING_DIR}/raspbian-shrink |
| } |
| |
| # Resize root partition to fill image size |
| function first_run_expand() { |
| ${RUN_ON_PI} raspi-config --expand-rootfs |
| ${RUN_ON_PI} reboot; sleep 1 |
| kill_qemu |
| } |
| |
| # Turn off swapfile |
| function disable_swap() { |
| ${RUN_ON_PI} dphys-swapfile swapoff |
| ${RUN_ON_PI} dphys-swapfile uninstall |
| ${RUN_ON_PI} systemctl disable dphys-swapfile.service |
| } |
| |
| # Clean up large footprint packages |
| function clean_unnecessary_packages() { |
| ${RUN_ON_PI} apt-get purge -qy \ |
| libraspberrypi-doc \ |
| avahi-daemon |
| } |
| |
| # Update APT sources and install required libraries |
| function run_apt_updates() { |
| ${RUN_ON_PI} apt-get -qy update |
| ${RUN_ON_PI} apt-get install -y --auto-remove libpulse-dev libasound2-dev \ |
| libavformat-dev libavresample-dev |
| # We are pulling in new deps from above, so lets fully upgrade. |
| ${RUN_ON_PI} apt-get -qy upgrade |
| } |
| |
| function install_cron_entry() { |
| # Fully replaces crontab, which has no other entries anyway |
| ${RUN_ON_PI} crontab /boot/crontab.txt |
| } |
| |
| function pre_shrink_cleanup() { |
| ${RUN_ON_PI} rm -rf /var/cache |
| ${RUN_ON_PI} sync |
| } |
| |
| # Run the shrink script |
| function shrink_image() { |
| tools_check |
| sudo ${STAGING_DIR}/raspbian-shrink ${IMG_FILE} \ |
| ${SHRUNK_IMG_FILE} |
| echo ===== |
| echo Final shrunk image available at: ${SHRUNK_IMG_FILE} |
| } |
| |
| function customize_on_qemu() { |
| run_qemu_backgrounded |
| wait_for_qemu |
| make_ssh_key |
| deploy_key |
| first_run_expand |
| run_qemu_backgrounded |
| wait_for_qemu |
| disable_swap |
| clean_unnecessary_packages |
| run_apt_updates |
| install_cron_entry |
| pre_shrink_cleanup |
| kill_qemu |
| } |
| |
| function prepare_raspi_image() { |
| tools_check |
| fetch_image |
| mount_root |
| modify_root |
| unmount_root |
| mount_boot |
| modify_boot |
| get_kernel |
| unmount_boot |
| customize_on_qemu |
| get_shrink_script |
| shrink_image |
| } |
| |
| # If script is executed in the subshell, launch the full |
| # customization script. |
| if [[ "${BASH_SOURCE[0]}" == "${0}" ]] ; then |
| echo "Running full image preparation script." |
| prepare_raspi_image |
| else |
| echo "Script is sourced in current shell, ready to execute individual steps." |
| fi |