#!/bin/sh # # PiLC bootstrap # # Copyright 2016-2017 Michael Buesch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # basedir="$(dirname "$0")" [ "$(echo "$basedir" | cut -c1)" = '/' ] || basedir="$PWD/$basedir" # The repository root is basedir. basedir="$basedir/.." MAIN_MIRROR="http://mirrordirector.raspbian.org/raspbian/" RPIGPIO_VERSION="0.6.3" RPIGPIO_MIRROR="https://netcologne.dl.sourceforge.net/project/raspberry-gpio-python/RPi.GPIO-$RPIGPIO_VERSION.tar.gz" RPIGPIO_SHA256="9366ff36104a39368759929e71f0d8ad6a88553497b3064cbc40f4248806cc19" die() { echo "=== $*" >&2 exit 1 } info() { echo "--- $*" } # print the first of its arguments. first() { echo "$1" } # print the last of its arguments. last() { while [ $# -gt 1 ]; do shift; done echo "$1" } # $1=program_name have_program() { which "$1" >/dev/null 2>&1 } # $1=program_name, ($2=description) assert_program() { local bin="$1" local desc="$2" [ -n "$desc" ] || desc="$bin" have_program "$bin" || die "$bin not found. Please install $desc." } term_signal() { die "Terminating signal received" } cleanup() { info "Cleaning up..." for mp in "$mp_shm" "$mp_proc" "$mp_sys" "$mp_bootimgfile" "$mp_rootimgfile"; do [ -n "$mp" -a -d "$mp" ] &&\ umount "$mp" >/dev/null 2>&1 done for mp in "$mp_bootimgfile" "$mp_rootimgfile"; do [ -n "$mp" -a -d "$mp" ] &&\ rmdir "$mp" >/dev/null 2>&1 done } boot_config_file() { cat < "$opt_target_dir/usr/sbin/policy-rc.d" ||\ die "Failed to create policy-rc.d" chmod 755 "$opt_target_dir/usr/sbin/policy-rc.d" ||\ die "Failed to chmod policy-rc.d" # Copy qemu. local qemu_bin="$opt_target_dir/$opt_qemu" if ! [ -x "$qemu_bin" ]; then info "Copying qemu binary from '$opt_qemu' to '$qemu_bin'..." mkdir -p "$(dirname "$qemu_bin")" ||\ die "Failed to make qemu base directory." cp "$opt_qemu" "$qemu_bin" ||\ die "Failed to copy qemu binary." fi info "Copying PiLC bootstrap script..." cp "$basedir/pilc/pilc-bootstrap.sh" "$opt_target_dir/" ||\ die "Failed to copy bootstrap script." info "Checking out awlsim..." local awlsim_dir="$opt_target_dir/tmp/awlsim" local checkout_dir="$awlsim_dir/src" rm -rf "$awlsim_dir" mkdir -p "$awlsim_dir" || die "mkdir failed" git clone --no-checkout "$basedir/.git" "$checkout_dir" ||\ die "Failed to clone" ( cd "$checkout_dir" ||\ die "Failed to cd" git checkout "$opt_branch" ||\ die "Failed to check out branch." git submodule update --init submodules/pyprofibus ||\ die "Failed to pull pyprofibus submodule" rm -r .git submodules/pyprofibus/.git ||\ die "Failed to remove .git directory." mv submodules/pyprofibus .. ||\ die "Failed to move pyprofibus submodule." ) || die # Fetch RPi.GPIO download "$opt_target_dir/tmp/RPi.GPIO-$RPIGPIO_VERSION.tar.gz" \ "$RPIGPIO_MIRROR" "$RPIGPIO_SHA256" # Copy kernel tree if [ "$opt_kernel" = "pilc" ]; then info "Copying PiLC kernel tree..." rsync -rlt --inplace --exclude='.git*' \ "$basedir/pilc/kernel/" "$opt_target_dir/tmp/kernel/" ||\ die "Failed to copy PiLC kernel." cp "$basedir/pilc/kernel.config" "$opt_target_dir/tmp/kernel/.config" ||\ die "Failed to copy PiLC kernel config." fi # Second stage will mount a few filesystems. # Keep track to umount them in cleanup. mp_proc="$opt_target_dir/proc" mp_sys="$opt_target_dir/sys" mp_shm="$opt_target_dir/dev/shm" } pilc_bootstrap_second_stage() { info "Running second stage ($opt_arch)..." [ -x /pilc-bootstrap.sh ] ||\ die "Second stage does not contain the bootstrap script." # Set up environment. export LC_ALL=C export LANGUAGE=C export LANG=C # debootstrap second stage. if [ $opt_skip_debootstrap2 -eq 0 ]; then info "Running debootstrap second stage..." /debootstrap/debootstrap --verbose --second-stage ||\ die "Debootstrap second stage failed." fi info "Mounting /proc..." mkdir -p /proc ||\ die "Failed to create /proc mountpoint." mount -t proc proc /proc ||\ die "Mounting /proc failed." info "Mounting /sys..." mkdir -p /sys ||\ die "Failed to create /sys mountpoint." mount -t sysfs sysfs /sys ||\ die "Mounting /sys failed." info "Mounting /dev/shm..." mkdir -p /dev/shm ||\ die "Failed to create /dev/shm mountpoint." mount -t tmpfs tmpfs /dev/shm ||\ die "Mounting /dev/shm failed." info "Writing apt configuration..." cat > /etc/apt/sources.list < /etc/apt/apt.conf.d/99no-translations ||\ die "Failed to set apt.conf.d" info "Creating /etc/fstab" mkdir -p /config ||\ die "Failed to create /config" cat > /etc/fstab < /etc/hostname ||\ die "Failed to set hostname" printf 'PiLC GNU/Linux (based on Raspbian) \\n \\l\n\n' > /etc/issue ||\ die "Failed to create /etc/issue" printf 'PiLC GNU/Linux (based on Raspbian)\n' > /etc/issue.net ||\ die "Failed to create /etc/issue.net" sed -i -e 's|PRETTY_NAME=.*|PRETTY_NAME="PiLC"|' \ /etc/os-release ||\ die "Failed to set os-release PRETTY_NAME." sed -i -e 's|NAME=.*|NAME="PiLC"|' \ /etc/os-release ||\ die "Failed to set os-release NAME." sed -i -e 's|ID=.*|ID=pilc|' \ /etc/os-release ||\ die "Failed to set os-release ID." sed -i -e 's|ID_LIKE=.*|ID_LIKE=raspbian|' \ /etc/os-release ||\ die "Failed to set os-release ID_LIKE." sed -i -e 's|HOME_URL=.*|HOME_URL="https://bues.ch/a/pilc"|' \ /etc/os-release ||\ die "Failed to set os-release HOME_URL." sed -i -e 's|SUPPORT_URL=.*|SUPPORT_URL="https://bues.ch/a/pilc"|' \ /etc/os-release ||\ die "Failed to set os-release SUPPORT_URL." sed -i -e 's|BUG_REPORT_URL=.*|BUG_REPORT_URL="https://bues.ch/a/pilc"|' \ /etc/os-release ||\ die "Failed to set os-release BUG_REPORT_URL." sed -i -e 's|#FSCKFIX=no|FSCKFIX=yes|' \ /etc/default/rcS ||\ die "Failed to set FSCKFIX=yes" info "Updating packages..." cat < /etc/ssh/sshd_not_to_be_run ||\ die "Failed to create /etc/ssh/sshd_not_to_be_run" echo 1 > /etc/ssh/ssh_create_keys ||\ die "Failed to create /etc/ssh/ssh_create_keys" info "Creating /etc/rc.local..." cat > /etc/rc.local < /sys/class/gpio/export /bin/echo in > /sys/class/gpio/gpio\${i}/direction done # Add HAT eeprom device. if ! [ -d "/sys/class/i2c-adapter/i2c-0/0-0050" ]; then /bin/echo "24c32 0x50" > /sys/class/i2c-adapter/i2c-0/new_device fi # Add /dev/ttyS0 link for convenience. if ! [ -e /dev/ttyS0 ]; then /bin/ln -s /dev/ttyAMA0 /dev/ttyS0 fi exit 0 EOF [ $? -eq 0 ] || die "Failed to create /etc/rc.local" info "Creating /etc/modules-load.d/i2c.conf..." cat > /etc/modules-load.d/i2c.conf < "/etc/sudoers.d/00-pi" ||\ die "Failed to create /etc/sudoers.d/00-pi" info "Initializing home directory..." mkdir -p /home/pi/.vim || die "Failed to mkdir /home/pi/.vim" cat > /home/pi/.vim/vimrc < /home/pi/.tmux.conf <\ /etc/systemd/system/awlsim-server.service ||\ die "Failed to create awlsim-server.service" systemctl enable awlsim-server.service ||\ die "Failed to enable awlsim-server-service" ) || die info "Building pyprofibus..." ( cd /tmp/awlsim/pyprofibus ||\ die "Failed to cd" debuild -uc -us -b -d || die "debuild failed" info "Built pyprofibus files:" ls .. || die "Failed to list results" info "Installing pyprofibus..." dpkg -i ../python-pyprofibus_*.deb ||\ die "Failed to install python-pyprofibus" dpkg -i ../python3-pyprofibus_*.deb ||\ die "Failed to install python3-pyprofibus" dpkg -i ../pypy-pyprofibus_*.deb ||\ die "Failed to install pypy-pyprofibus" dpkg -i ../profisniff_*.deb ||\ die "Failed to install profisniff" dpkg -i ../gsdparser_*.deb ||\ die "Failed to install gsdparser" dpkg -i ../pyprofibus-linuxcnc-hal_*.deb ||\ die "Failed to install pyprofibus-linuxcnc-hal" ) || die rm -r /tmp/awlsim ||\ die "Failed to remove awlsim checkout." info "Extending pi user environment..." cat >> /home/pi/.bashrc < /etc/network/interfaces.d/lo < /etc/network/interfaces.d/eth$i < "$opt_target_dir/boot/cmdline.txt" < "$opt_target_dir/boot/config.txt" ||\ die "Failed to create /boot/config.txt" local img="$(first "$opt_target_dir/boot/"vmlinuz-*-rpi)" if [ -e "$img" ]; then mv "$img" "$opt_target_dir/boot/kernel.img" ||\ die "Failed to create kernel.img" fi local img="$(first "$opt_target_dir/boot/"initrd.img-*-rpi)" if [ -e "$img" ]; then mv "$img" "$opt_target_dir/boot/initrd.img" ||\ die "Failed to create initrd.img" fi local img="$(first "$opt_target_dir/boot/"vmlinuz-*-rpi2)" if [ -e "$img" ]; then mv "$img" "$opt_target_dir/boot/kernel7.img" ||\ die "Failed to create kernel7.img" fi local img="$(first "$opt_target_dir/boot/"initrd.img-*-rpi2)" if [ -e "$img" ]; then mv "$img" "$opt_target_dir/boot/initrd7.img" ||\ die "Failed to create initrd7.img" fi # Prepare image paths. local target_dir="$(readlink -m "${opt_target_dir}")" [ -n "$target_dir" ] || die "Failed to resolve target dir." local imgfile="${target_dir}${opt_imgsuffix}.img" local imgfile_zip="${imgfile}.7z" local bootimgfile="${imgfile}.boot" mp_bootimgfile="${bootimgfile}.mp" local rootimgfile="${imgfile}.root" mp_rootimgfile="${rootimgfile}.mp" rm -f "$imgfile" "$imgfile_zip" "$rootimgfile" "$bootimgfile" rmdir "$mp_bootimgfile" "$mp_rootimgfile" 2>/dev/null # Create images. if [ "$opt_img" -ne 0 ]; then info "Creating boot image..." mkfs.vfat -F 32 -i 7771B0BB -n boot -C "$bootimgfile" \ $(expr \( 64 \* 1024 \) - \( 4 \* 1024 \) ) ||\ die "Failed to create boot partition file system." mkdir "$mp_bootimgfile" ||\ die "Failed to make boot partition mount point." mount -o loop "$bootimgfile" "$mp_bootimgfile" ||\ die "Failed to mount boot partition." rsync -aHAX --inplace \ "$target_dir/boot/" "$mp_bootimgfile/" ||\ die "Failed to copy boot files." umount "$mp_bootimgfile" ||\ die "Failed to umount boot partition." rmdir "$mp_bootimgfile" ||\ die "Failed to remove boot partition mount point." info "Creating root image..." mkfs.ext4 "$rootimgfile" $(expr \( 4000 - 64 \) \* 1024 ) ||\ die "Failed to create root filesystem." mkdir "$mp_rootimgfile" ||\ die "Failed to make root partition mount point." mount -o loop "$rootimgfile" "$mp_rootimgfile" ||\ die "Failed to mount root partition." rsync -aHAX --inplace \ --exclude='boot/*' \ --exclude='proc/*' \ --exclude='sys/*' \ --exclude='dev/shm/*' \ --exclude='tmp/*' \ --exclude="$(basename "$opt_qemu")" \ "$target_dir/" "$mp_rootimgfile/" ||\ die "Failed to copy root files." umount "$mp_rootimgfile" ||\ die "Failed to umount root partition." rmdir "$mp_rootimgfile" ||\ die "Failed to remove root partition mount point." info "Creating image '$imgfile'..." dd if=/dev/zero of="$imgfile" bs=1M count=4000 conv=sparse ||\ die "Failed to create image file." parted "$imgfile" < raspberrypi-bootloader package (default)" echo " pilc -> PiLC kernel" echo echo " --qemu-bin|-q PATH Select qemu-user-static binary." echo " Default: $default_qemu" echo echo " --img-suffix|-s SUFFIX Image file suffix." echo " Default: $default_imgsuffix" echo echo " --no-img|-I Do not create an image." echo " Default: Create image." echo echo " --no-zimg|-Z Do not create a 7zipped image." echo " Default: Create 7zipped image." echo echo " --skip-debootstrap1|-1 Skip debootstrap first stage." echo " --skip-debootstrap2|-2 Skip debootstrap second stage." } # canonicalize basedir basedir="$(readlink -e "$basedir")" [ -n "$basedir" ] || die "Failed to canonicalize base directory." # Mountpoints. Will be umounted on cleanup. mp_shm= mp_proc= mp_sys= mp_bootimgfile= mp_rootimgfile= trap term_signal TERM INT if [ -z "$__PILC_BOOTSTRAP_SECOND_STAGE__" ]; then # First stage trap cleanup EXIT export _NPROCESSORS_ONLN="$(getconf _NPROCESSORS_ONLN)" [ -n "$_NPROCESSORS_ONLN" ] || die "Failed to get # of online CPUs" default_branch="master" default_suite="jessie" default_arch="armhf" default_kernel="raspi" default_qemu="/usr/bin/qemu-arm-static" default_imgsuffix="-$(date '+%Y%m%d')" default_img=1 default_zimg=1 opt_target_dir= opt_branch="$default_branch" opt_cython=1 opt_suite="$default_suite" opt_arch="$default_arch" opt_kernel="$default_kernel" opt_qemu="$default_qemu" opt_skip_debootstrap1=0 opt_skip_debootstrap2=0 opt_imgsuffix="$default_imgsuffix" opt_img="$default_img" opt_zimg="$default_zimg" while [ $# -ge 1 ]; do case "$1" in --help|-h) usage exit 0 ;; --branch|-b) shift opt_branch="$1" [ -n "$opt_branch" ] || die "No branch given" ;; --no-cython|-C) opt_cython=0 ;; --suite|-s) shift opt_suite="$1" [ -n "$opt_suite" ] || die "No suite given" ;; --arch|-a) shift opt_arch="$1" [ -n "$opt_arch" ] || die "No arch given" ;; --kernel|-k) shift opt_kernel="$1" [ "$opt_kernel" = "raspi" -o \ "$opt_kernel" = "pilc" ] || die "Invalid kernel" ;; --qemu-bin|-q) shift opt_qemu="$1" [ -x "$opt_qemu" ] || die "No valid qemu binary given" ;; --skip-debootstrap1|-1) opt_skip_debootstrap1=1 ;; --skip-debootstrap2|-2) opt_skip_debootstrap2=1 ;; --img-suffix|-s) shift opt_imgsuffix="$1" ;; --no-zimg|-Z) opt_zimg=0 ;; --no-img|-I) opt_img=0 ;; *) opt_target_dir="$*" break ;; esac shift done [ -n "$opt_target_dir" ] ||\ die "No TARGET_DIR" [ -d "$opt_target_dir" -o ! -e "$opt_target_dir" ] ||\ die "$opt_target_dir is not a directory" # Run first stage. pilc_bootstrap_first_stage info "Starting second stage." # Export options for use by second stage. export opt_target_dir export opt_branch export opt_cython export opt_suite export opt_arch export opt_kernel export opt_qemu export opt_skip_debootstrap1 export opt_skip_debootstrap2 export opt_imgsuffix export opt_zimg export opt_img export __PILC_BOOTSTRAP_SECOND_STAGE__=1 chroot "$opt_target_dir" "/pilc-bootstrap.sh" ||\ die "Chroot failed." # Run third stage. pilc_bootstrap_third_stage info "" info "Successfully bootstrapped PiLC." exit 0 else # Run second stage pilc_bootstrap_second_stage exit 0 fi