#!/bin/bash # Library to install software on Raspbian ARM through QEMU # # Copyleft 2017 by Ignacio Nunez Hernanz # GPL licensed (see end of file) * Use at your own risk! # # More at ownyourbits.com # DBG=x version=$(git describe --tags --always) version=${version%-*-*} # $IMG is the source image # $IP is the IP of the QEMU images # $IMGOUT will contain the name of the generated image function launch_install_qemu() { local IMG=$1 local IP=$2 [[ "$IP" == "" ]] && { echo "usage: launch_install_qemu "; return 1; } test -f "$IMG" || { echo "input file $IMG not found"; return 1; } IMGOUT="$IMG-$( date +%s )" cp --reflink=auto -v "$IMG" "$IMGOUT" || return 1 pgrep qemu-system-aarch64 &>/dev/null && { echo -e "QEMU instance already running. Abort..."; return 1; } launch_qemu "$IMGOUT" & sleep 10 wait_SSH "$IP" launch_installation_qemu "$IP" || return 1 # uses $INSTALLATION_CODE wait echo "$IMGOUT generated successfully" } function launch_qemu() { local IMG=$1 test -f "$1" || { echo "Image $IMG not found"; return 1; } test -d qemu-raspbian-network || git clone https://github.com/nachoparker/qemu-raspbian-network.git sed -i '30s/NO_NETWORK=1/NO_NETWORK=0/' qemu-raspbian-network/qemu-pi.sh sed -i '35s/NO_GRAPHIC=0/NO_GRAPHIC=1/' qemu-raspbian-network/qemu-pi.sh echo "Starting QEMU image $IMG" ( cd qemu-raspbian-network && sudo ./qemu-pi.sh ../"$IMG" 2>/dev/null ) } function ssh_pi() { local IP=$1 local ARGS=${@:2} local PIUSER=${PIUSER:-pi} local PIPASS=${PIPASS:-raspberry} local SSH=( ssh -q -o UserKnownHostsFile=/dev/null\ -o StrictHostKeyChecking=no\ -o ServerAliveInterval=20\ -o ConnectTimeout=20\ -o LogLevel=quiet ) type sshpass &>/dev/null && local SSHPASS=( sshpass -p$PIPASS ) if [[ "${SSHPASS[@]}" == "" ]]; then ${SSH[@]} ${PIUSER}@$IP $ARGS; else ${SSHPASS[@]} ${SSH[@]} ${PIUSER}@$IP $ARGS local RET=$? [[ $RET -eq 5 ]] && { ${SSH[@]} ${PIUSER}@$IP $ARGS; return $?; } return $RET fi } function wait_SSH() { local IP=$1 echo "Waiting for SSH to be up on $IP..." while true; do ssh_pi "$IP" : && break sleep 1 done echo "SSH is up" } function launch_installation() { local IP=$1 [[ "$INSTALLATION_CODE" == "" ]] && { echo "Need to run config first" ; return 1; } [[ "$INSTALLATION_STEPS" == "" ]] && { echo "No installation instructions"; return 1; } local PREINST_CODE=" set -e$DBG sudo su set -e$DBG " echo "Launching installation" echo -e "$PREINST_CODE\n$INSTALLATION_CODE\n$INSTALLATION_STEPS" | ssh_pi "$IP" || { echo "Installation to $IP failed" && return 1; } } function launch_installation_qemu() { local IP=$1 [[ "$NO_CFG_STEP" != "1" ]] && local CFG_STEP=configure [[ "$NO_CLEANUP" != "1" ]] && local CLEANUP_STEP="if [[ \$( type -t cleanup ) == function ]];then cleanup; fi" [[ "$NO_HALT_STEP" != "1" ]] && local HALT_STEP="nohup halt &>/dev/null &" local INSTALLATION_STEPS=" install $CFG_STEP $CLEANUP_STEP $HALT_STEP " launch_installation "$IP" # uses $INSTALLATION_CODE } function launch_installation_online() { local IP=$1 [[ "$NO_CFG_STEP" != "1" ]] && local CFG_STEP=configure local INSTALLATION_STEPS=" install $CFG_STEP " launch_installation "$IP" # uses $INSTALLATION_CODE } function prepare_dirs() { [[ "$CLEAN" == "0" ]] || rm -rf cache rm -rf tmp mkdir -p tmp output cache } function mount_raspbian() { local IMG="$1" local MP=raspbian_root [[ -f "$IMG" ]] || { echo "no image"; return 1; } [[ -e "$MP" ]] && { echo "$MP already exists"; return 1; } local SECTOR=$( fdisk -l "$IMG" | grep Linux | awk '{ print $2 }' ) local OFFSET=$(( SECTOR * 512 )) mkdir -p "$MP" sudo mount $IMG -o offset=$OFFSET "$MP" || return 1 echo "RaspiOS image mounted" } function mount_raspbian_boot() { local IMG="$1" local MP=raspbian_boot [[ -f "$IMG" ]] || { echo "no image"; return 1; } [[ -e "$MP" ]] && { echo "$MP already exists"; return 1; } local SECTOR=$( fdisk -l "$IMG" | grep FAT32 | awk '{ print $2 }' ) local OFFSET=$(( SECTOR * 512 )) mkdir -p "$MP" sudo mount $IMG -o offset=$OFFSET "$MP" || return 1 echo "RaspiOS image mounted" } function umount_raspbian() { [[ -d raspbian_root ]] || [[ -d raspbian_boot ]] || { echo "Nothing to umount"; return 0; } [[ -d raspbian_root ]] && { sudo umount -l raspbian_root; rmdir raspbian_root || return 1; } [[ -d raspbian_boot ]] && { sudo umount -l raspbian_boot; rmdir raspbian_boot || return 1; } echo "RaspiOS image umounted" } function prepare_chroot_raspbian() { local IMG="$1" mount_raspbian "$IMG" || return 1 sudo mount -t proc proc raspbian_root/proc/ sudo mount -t sysfs sys raspbian_root/sys/ sudo mount -o bind /dev raspbian_root/dev/ sudo mount -o bind /dev/pts raspbian_root/dev/pts if [[ -f "qemu-aarch64-static" ]] then sudo cp qemu-aarch64-static raspbian_root/usr/bin/ #sudo cp qemu-arm-static raspbian_root/usr/bin/ else sudo cp /usr/bin/qemu-aarch64-static raspbian_root/usr/bin #sudo cp /usr/bin/qemu-arm-static raspbian_root/usr/bin fi # Prevent services from auto-starting sudo bash -c "echo -e '#!/bin/sh\nexit 101' > raspbian_root/usr/sbin/policy-rc.d" sudo chmod +x raspbian_root/usr/sbin/policy-rc.d } function clean_chroot_raspbian() { sudo rm -f raspbian_root/usr/bin/qemu-aarch64-static sudo rm -f raspbian_root/usr/sbin/policy-rc.d sudo umount -l raspbian_root/{proc,sys,dev/pts,dev} umount_raspbian } # sets DEV function resize_image() { local IMG="$1" local SIZE="$2" local DEV echo -e "\n\e[1m[ Resize Image ]\e[0m" fallocate -l$SIZE "$IMG" parted "$IMG" -- resizepart 2 -1s DEV="$( sudo losetup -f )" mount_raspbian "$IMG" sudo resize2fs -f "$DEV" echo "Image resized" umount_raspbian } function update_boot_uuid() { local IMG="$1" local PTUUID="$( sudo blkid -o export "$IMG" | grep PTUUID | sed 's|.*=||' )" echo -e "\n\e[1m[ Update Raspbian Boot UUIDS ]\e[0m" mount_raspbian "$IMG" || return 1 sudo bash -c "cat > raspbian_root/etc/fstab" < raspbian_root/etc/dhcpcd.conf" < "$IMG_CACHE" && \ cp -v --reflink=auto $IMG_CACHE "$IMGFILE" } function pack_image() { local IMG="$1" local TAR="$2" local DIR="$( dirname "$IMG" )" local IMGNAME="$( basename "$IMG" )" echo -e "\n\e[1m[ Pack Image ]\e[0m" echo "packing $IMG → $TAR" tar -C "$DIR" -cavf "$TAR" "$IMGNAME" && \ echo -e "$TAR packed successfully" } function create_torrent() { local TAR="$1" echo -e "\n\e[1m[ Create Torrent ]\e[0m" [[ -f "$TAR" ]] || { echo "image $TAR not found"; return 1; } local IMGNAME="$( basename "$TAR" .tar.bz2 )" local DIR="torrent/$IMGNAME" [[ -d "$DIR" ]] && { echo "dir $DIR already exists"; return 1; } mkdir -p torrent/"$IMGNAME" && cp -v --reflink=auto "$TAR" torrent/"$IMGNAME" md5sum "$DIR"/*.bz2 > "$DIR"/md5sum createtorrent -a udp://tracker.opentrackr.org -p 1337 -c "NextCloudPi. Nextcloud ready to use image" "$DIR" "$DIR".torrent transmission-remote -w $(pwd)/torrent -a "$DIR".torrent } function generate_changelog() { git log --graph --oneline --decorate \ --pretty=format:"[%<(13)%D](https://github.com/nextcloud/nextcloudpi/commit/%h) (%ad) %s" --date=short | \ grep 'tag: v' | \ sed '/HEAD ->\|origin/s|\[.*\(tag: v[0-9]\+\.[0-9]\+\.[0-9]\+\).*\]|[\1]|' | \ sed 's|* \[tag: |\n[|' > changelog.md } upload_images() { test -d output || { echo "No uploads found. Nothing to do"; return; } [[ "$FTPPASS" == "" ]] && { echo "No FTPPASS variable found, skip upload"; return 1; } mkdir -p archive for img in $(find output -name '*.tar.bz2'); do upload_ftp "$(basename ${img} .tar.bz2)" && mv "${img}" archive done } function upload_docker() { export DOCKER_CLI_EXPERIMENTAL=enabled docker push ownyourbits/nextcloudpi-x86:latest docker push ownyourbits/nextcloudpi-x86:${version} docker push ownyourbits/nextcloud-x86:latest docker push ownyourbits/nextcloud-x86:${version} docker push ownyourbits/lamp-x86:latest docker push ownyourbits/lamp-x86:${version} docker push ownyourbits/debian-ncp-x86:latest docker push ownyourbits/debian-ncp-x86:${version} docker push ownyourbits/nextcloudpi-armhf:latest docker push ownyourbits/nextcloudpi-armhf:${version} docker push ownyourbits/nextcloud-armhf:latest docker push ownyourbits/nextcloud-armhf:${version} docker push ownyourbits/lamp-armhf:latest docker push ownyourbits/lamp-armhf:${version} docker push ownyourbits/debian-ncp-armhf:latest docker push ownyourbits/debian-ncp-armhf:${version} docker push ownyourbits/nextcloudpi-arm64:latest docker push ownyourbits/nextcloudpi-arm64:${version} docker push ownyourbits/nextcloud-arm64:latest docker push ownyourbits/nextcloud-arm64:${version} docker push ownyourbits/lamp-arm64:latest docker push ownyourbits/lamp-arm64:${version} docker push ownyourbits/debian-ncp-arm64:latest docker push ownyourbits/debian-ncp-arm64:${version} # Docker multi-arch docker manifest create --amend ownyourbits/nextcloudpi:${version} \ --amend ownyourbits/nextcloudpi-x86:${version} \ --amend ownyourbits/nextcloudpi-armhf:${version} \ --amend ownyourbits/nextcloudpi-arm64:${version} docker manifest create --amend ownyourbits/nextcloudpi:latest \ --amend ownyourbits/nextcloudpi-x86:latest \ --amend ownyourbits/nextcloudpi-armhf:latest \ --amend ownyourbits/nextcloudpi-arm64:latest docker manifest annotate ownyourbits/nextcloudpi:${version} ownyourbits/nextcloudpi-x86:${version} --os linux --arch amd64 docker manifest annotate ownyourbits/nextcloudpi:${version} ownyourbits/nextcloudpi-armhf:${version} --os linux --arch arm docker manifest annotate ownyourbits/nextcloudpi:${version} ownyourbits/nextcloudpi-arm64:${version} --os linux --arch arm64 docker manifest annotate ownyourbits/nextcloudpi:latest ownyourbits/nextcloudpi-x86:latest --os linux --arch amd64 docker manifest annotate ownyourbits/nextcloudpi:latest ownyourbits/nextcloudpi-armhf:latest --os linux --arch arm docker manifest annotate ownyourbits/nextcloudpi:latest ownyourbits/nextcloudpi-arm64:latest --os linux --arch arm64 docker manifest push -p ownyourbits/nextcloudpi:${version} docker manifest push -p ownyourbits/nextcloudpi:latest } function test_docker() { ( cd build/docker docker compose down docker volume rm docker_ncdata docker compose up -d sleep 30 ../../tests/activation_tests.py ../../tests/nextcloud_tests.py ../../tests/system_tests.py docker compose down ) } function test_lxc() { local ip lxc stop ncp || true lxc start ncp lxc exec ncp -- bash -c 'while [ "$(systemctl is-system-running 2>/dev/null)" != "running" ] && [ "$(systemctl is-system-running 2>/dev/null)" != "degraded" ]; do :; done' ip="$(lxc exec ncp -- bash -c 'source /usr/local/etc/library.sh && get_ip')" tests/activation_tests.py "${ip}" tests/nextcloud_tests.py "${ip}" tests/system_tests.py lxc stop ncp } function test_vm() { local ip virsh --connect qemu:///system shutdown ncp-vm &>/dev/null || true virsh --connect qemu:///system start ncp-vm while [[ "${ip}" == "" ]]; do ip="$(virsh --connect qemu:///system domifaddr ncp-vm | grep ipv4 | awk '{ print $4 }' | sed 's|/24||' )" sleep 0.5 done tests/activation_tests.py "${ip}" tests/nextcloud_tests.py "${ip}" #tests/system_tests.py virsh --connect qemu:///system shutdown ncp-vm } # License # # This script 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 script 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 script; if not, write to the # Free Software Foundation, Inc., 59 Temple Place, Suite 330, # Boston, MA 02111-1307 USA