Created
May 1, 2019 00:31
-
-
Save flisboac/5dc1984154f9fafb3d83c78503bcc4b5 to your computer and use it in GitHub Desktop.
A simple and quick frankensteined/projectified Raspbian/RaspberryPi provisioner and tester helper sorta-thing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
RPI_PIVERSION="${RPI_PIVERSION:-""}" | |
RPI_PIMODEL="${RPI_PIMODEL:-""}" | |
RPI_IMAGE_URLPREFIX="${RPI_IMAGE_URLPREFIX:-"https://downloads.raspberrypi.org/raspbian_"}" | |
RPI_IMAGE_VARIANT="${RPI_IMAGE_VARIANT:-"lite"}" | |
RPI_IMAGE_VERSION="latest" | |
RPI_GUEST_SHELL="${RPI_GUEST_SHELL:-"sh"}" | |
RPI_QEMUFIX_REPO="https://github.com/dhruvvyas90/qemu-rpi-kernel" | |
RPI_QEMUFIX_VERSION="4.14.79-stretch" | |
RPI_BINFMTEXEC="/usr/bin/qemu-arm-static" | |
RPI_CURL="${RPI_CURL:-"curl"}" | |
RPI_UNZIP="${RPI_UNZIP:-"unzip"}" | |
RPI_GIT="${RPI_GIT:-"git"}" | |
RPI_DEPS_debian="qemu binfmt-support qemu-user-static curl unzip klibc-utils systemd-container" | |
RPI_DEPS_ubuntu="$RPI_DEPS_debian" | |
RPI_DEPS_arch="qemu qemu-arch-extra binfmt-qemu-static qemu-user-static curl unzip" | |
RPI_INSTALLER_arch="yay" | |
# --- | |
println() { | |
local fmt | |
fmt="$1"; shift | |
printf "${fmt}\n" "$@" | |
} | |
eprintln() { | |
local fmt | |
fmt="$1"; shift | |
printf "${fmt}\n" "$@" >&2 | |
} | |
FALSE=1 | |
TRUE=0 | |
log_TRACE=0 | |
log_DEBUG=10 | |
log_INFO=20 | |
log_WARN=30 | |
log_ERROR=40 | |
log_ABORT=50 | |
log_LEVEL="$log_INFO" | |
log_leveltostr() { | |
if [ "$1" -le "$log_TRACE" ]; then printf "TRACE"; return 0; fi | |
if [ "$1" -le "$log_DEBUG" ]; then printf "DEBUG"; return 0; fi | |
if [ "$1" -le "$log_INFO" ]; then printf " INFO"; return 0; fi | |
if [ "$1" -le "$log_WARN" ]; then printf " WARN"; return 0; fi | |
if [ "$1" -le "$log_ERROR" ]; then printf "ERROR"; return 0; fi | |
printf "ABORT" | |
} | |
log() { | |
local __lvl | |
local __fmt | |
__lvl="$1"; shift | |
__fmt="$1"; shift | |
if [ "$__lvl" -ge "$log_LEVEL" ]; then | |
eprintln "*** [$(log_leveltostr "$__lvl")] $__fmt" "$@" >&2 | |
fi | |
} | |
trace() { local __code="$?"; log "$log_TRACE" "$@"; return "${__code:-"0"}"; } | |
debug() { local __code="$?"; log "$log_DEBUG" "$@"; return "${__code:-"0"}"; } | |
info() { local __code="$?"; log "$log_INFO" "$@"; return "${__code:-"0"}"; } | |
warn() { local __code="$?"; log "$log_WARN" "$@"; return "${__code:-"0"}"; } | |
error() { local __code="$?"; log "$log_ERROR" "$@"; return "${__code:-"0"}"; } | |
abort() { | |
local code | |
local last="${?:-0}" | |
if [ "$#" -gt 0 ]; then | |
code="$1"; shift | |
elif [ "$last" -ne 0 ]; then | |
code="$last" | |
else | |
code="1" | |
fi | |
[ "$#" -gt 0 ] && log "$log_ABORT" "$@" | |
exit "$code" | |
} | |
die() { | |
local code | |
local last="${?:-0}" | |
if [ "$last" -ne 0 ]; then | |
code="$last" | |
else | |
code="1" | |
fi | |
[ "$#" -gt 0 ] && log "$log_ABORT" "$@" | |
exit "$code" | |
} | |
current_shell() { | |
ps -p $$ -ocomm= | |
} | |
typeof_shell() { | |
local shell_name | |
[ ! -z "$BASH" ] && echo "bash" && return | |
[ ! -z "$ZSH_NAME" ] && echo "zsh" && return | |
shell_name="$(current_shell)" | |
shell_name="$(basename "$shell_name" 2>/dev/null)" | |
[ ! -z "$shell_name" ] && printf "%s\n" "$shell_name" && return | |
# May never reach here | |
echo "sh" | |
} | |
set_posix() { | |
case "$(typeof_shell)" in | |
bash) set -o posix ;; | |
esac | |
} | |
unset_posix() { | |
case "$(typeof_shell)" in | |
bash) set +o posix ;; | |
esac | |
} | |
[ -z "$SHELL_HAS_TYPE_T" ] && SHELL_HAS_TYPE_T="$(type -t 'if' 2>/dev/null >/dev/null; echo "$?")" | |
typeof() { | |
local elem; elem="$1"; shift | |
local typeinfo | |
local typeid | |
local exitcode | |
if [ "$SHELL_HAS_TYPE_T" -eq 0 ]; then | |
type -t "$elem" | |
exitcode="$?" | |
else | |
typeinfo="$(LANG=C command -V "$elem" 2>/dev/null)" | |
exitcode="$?" | |
typeinfo="$(echo "$typeinfo" | sed 's/^.*is //')" | |
if [ "$exitcode" -eq 0 ]; then | |
if ( echo "$typeinfo" | grep "a shell keyword" >/dev/null ); then echo "keyword" | |
elif ( echo "$typeinfo" | grep "a shell function" >/dev/null ); then echo "function" | |
elif ( echo "$typeinfo" | grep "is an alias for" >/dev/null ); then echo "alias" | |
elif ( echo "$typeinfo" | grep "a shell builtin" >/dev/null ); then echo "builtin" | |
else echo "file" | |
fi | |
fi | |
fi | |
if [ "$exitcode" -ne 0 ]; then | |
if is_var "$elem"; then | |
echo "var" | |
exitcode=0 | |
fi | |
fi | |
return "$exitcode" | |
} | |
is_var() { | |
( | |
set_posix | |
set | grep "^$1=" >/dev/null 2>/dev/null | |
) | |
} | |
is_function() { | |
[ "$(typeof "$1")" = "function" ] && return $YES | |
return $NO | |
} | |
os_id() { | |
[ "$(command -v lsb_release >/dev/null; echo "$?")" -ne 0 ] && return 1 | |
lsb_release -is | tr "[:upper:]" "[:lower:]" | |
} | |
# --- | |
rpi_util_makedownloadurl() { | |
local __suffix | |
local __version="${rpi_prj_distroversion}" | |
if [ -z "$rpi_prj_distrovariant" ] || [ "$rpi_prj_distrovariant" = "desktop" ]; then | |
__suffix="$__version" | |
else | |
__suffix="${rpi_prj_distrovariant}_${__version}" | |
fi | |
printf "${rpi_prj_urlprefix}%s" "$__suffix" | |
} | |
rpi_util_iscmd() { | |
printf "$RPI_COMMANDS" | grep -E "^\\s*$1" >/dev/null | |
} | |
rpi_util_cmdusage() { | |
printf "$RPI_COMMANDS" | grep -E "^\\s*$1" | sed -E 's%^\s*([^:]*):([^:]*):(.*)$%\2' | |
} | |
rpi_util_cmdbrief() { | |
printf "$RPI_COMMANDS" | grep -E "^\\s*$1" | sed -E 's%^\s*([^:]*):([^:]*):(.*)$%\3' | |
} | |
rpi_consumearg() { | |
rpi_SHIFT=$((rpi_SHIFT+1)) | |
} | |
__rpi_runcmd_subshell() { | |
local __function | |
trace "Running command '%s' in a subshell" "$__cmd" | |
trace "Running phase 'parse' of command: %s" "$__cmd" | |
{ | |
__function="rpi_cmd_${__cmd}_parse" | |
if is_function "$__function"; then "$__function" "$@"; fi | |
} && ( | |
{ | |
rpi_run_phase="pre" | |
__function="rpi_cmd_${__cmd}_${rpi_run_phase}" | |
trace "Running phase '${rpi_run_phase}' of command: %s" "$__cmd" && \ | |
{ if is_function "$__function"; then "$__function"; fi } | |
} && { | |
rpi_run_phase="run" | |
__function="rpi_cmd_${__cmd}" | |
trace "Running phase '${rpi_run_phase}' of command: %s" "$__cmd" && \ | |
"$__function" "$@" | |
} && { | |
rpi_run_phase="post" | |
__function="rpi_cmd_${__cmd}_${rpi_run_phase}" | |
trace "Running phase '${rpi_run_phase}' of command: %s" "$__cmd" && \ | |
{ if is_function "$__function"; then "$__function"; fi } | |
} | |
) | |
} | |
__rpi_runcmd_safe() { | |
local __function | |
trace "Running command '%s'" "$__cmd" | |
trace "Running phase 'parse' of command: %s" "$__cmd" | |
__function="rpi_cmd_${__cmd}_parse" && \ | |
{ if is_function "$__function"; then "$__function" "$@"; fi; } && \ | |
\ | |
rpi_run_phase="pre" && \ | |
__function="rpi_cmd_${__cmd}_${rpi_run_phase}" && \ | |
trace "Running phase '${rpi_run_phase}' of command: %s" "$__cmd" && \ | |
{ if is_function "$__function"; then "$__function"; fi; } && \ | |
\ | |
rpi_run_phase="run" && \ | |
__function="rpi_cmd_${__cmd}" && \ | |
trace "Running phase '${rpi_run_phase}' of command: %s" "$__cmd" && \ | |
"$__function" "$@" && \ | |
\ | |
rpi_run_phase="post" && \ | |
__function="rpi_cmd_${__cmd}_${rpi_run_phase}" && \ | |
trace "Running phase '${rpi_run_phase}' of command: %s" "$__cmd" && \ | |
{ if is_function "$__function"; then "$__function"; fi; } | |
} | |
rpi_runcmd() { | |
local __cmd | |
local __var | |
local __nosubshell | |
__cmd="$1"; shift | |
! rpi_util_iscmd "$__cmd" && die "Invalid command '$__cmd'." | |
! is_function "rpi_cmd_${__cmd}" && die "Command '$__cmd' is not implemented!" | |
__var="rpi_cmd_${__cmd}_nosubshell" | |
__nosubshell="$(is_var "$__var" && eval "echo \"\$$__var\"")" | |
if [ ! -z "$__nosubshell" ] && [ "$__nosubshell" = "$TRUE" ]; then | |
__rpi_runcmd_safe "$@" | |
else | |
__rpi_runcmd_subshell "$@" | |
fi | |
} | |
# --- | |
RPI_NAME="rpi" | |
RPI_VERSION="0.1.0" | |
RPI_DESCRIPTION="A Raspbian image customizer helper sorta-thing." | |
{ RPI_COMMANDS="$(cat)"; } <<-RPI_COMMANDS_EOC | |
help:<-h|help> [COMMAND]:Shows the help content for this program or for its commands. | |
installdeps:installdeps:Install runtime dependencies needed for preparing and running the RPi environment/chroot. | |
init:init:Initializes a new RPi project. | |
projdir:projdir:Prints the root directory of the current RPi project, if any. | |
mntdir:mntdir:Prints the directory where the RPi image will be mounted -- no chroot involved! | |
configure:configure:Lays out the structure and downloads the files needed to mount, run and build the Rpi project. Will init a project, if needed. | |
mount:mount:Mounts the RPi image. Configures the project, if needed. | |
expand:expand <INT_AMOUNT_IN_MB>:Expands the root partition by the specified amount, in MEGABYTES. NOTE: DO NOT USE prefixes or suffixes! | |
chroot:chroot [SHELL_ARGS...]:Chroots into the mounted RPi project. (Mounts the project, if needed.) | |
build:build:Builds the project by running the configured provision script in a chroot environment. | |
cp:cp <DEST>:Copies the RPi image to the provided destination. | |
burn:burn <DEVICE>:Burns a SD card with the downloaded/edited RPi image. | |
emulate:emulate:Runs the image in QEMU. Configures the project, if needed. | |
umount:unmount:Unmounts the RPi image. | |
clean:clean:Unmounts and removes the RPi image; does not delete the downloaded zipped image file, if present. | |
distclean:distclean:Removes any runtime file/directory created by other commands. | |
RPI_COMMANDS_EOC | |
{ RPI_FILE_RPIRESOURCE="$(cat)"; } <<RPI_RPISH_FILE_EOC | |
#!/bin/sh | |
rpirc_provisioner="rpi-provision.sh" | |
RPI_RPISH_FILE_EOC | |
{ RPI_FILE_RPIPROVISION="$(cat)"; } <<RPI_RPISH_FILE_EOC | |
#!/bin/sh | |
exec apt-get -y upgrade \\ | |
&& apt-get -y autoclean | |
RPI_RPISH_FILE_EOC | |
# --- | |
_rpi_do_listcmds() { | |
local __ind=" " | |
printf "$RPI_COMMANDS" | sed -E "s%\ | |
^\\s*([^:]*):([^:]*):(.*)$%\ | |
${__ind}\\1\n${__ind}${__ind}\\3\n${__ind}${__ind}USAGE: $(basename "$0") \\2%g" | |
} | |
_rpi_do_parseopts() { | |
local __stop | |
__stop="$FALSE" | |
while [ "$#" -gt 0 ] && [ "$__stop" -ne "$TRUE" ]; do | |
case "$1" in | |
-y|--noconfirm) rpi_opt_confirmed="$TRUE"; shift; rpi_consumearg ;; | |
-V|--version) println "%s" "$RPI_VERSION"; exit 0 ;; | |
-h|--help) rpi_cmd_help "$@"; exit 0 ;; | |
-vv) log_LEVEL="$log_TRACE"; shift; rpi_consumearg ;; | |
-v|--verbose) log_LEVEL="$log_DEBUG"; shift; rpi_consumearg ;; | |
*) __stop="$TRUE" ;; | |
esac | |
done | |
} | |
_rpi_do_initvars() { | |
local __pwd | |
local __init | |
__init="$1"; shift | |
[ "$#" -gt 0 ] && __pwd="$1" && shift | |
if [ ! -z "$__pwd" ]; then | |
rpi_prj_root="$__pwd" | |
rpi_prj_rcfile="${rpi_prj_root}/${rpi_cfg_rcfilename}" | |
if [ -z "$rpi_run_root" ]; then | |
rpi_run_root="${rpi_prj_root}/${rpi_cfg_rundirname}" | |
rpi_run_mntroot="$rpi_run_root/mnt" | |
rpi_run_envroot="$rpi_run_root/cache.env.d" | |
rpi_run_imgroot="$rpi_run_root" | |
fi | |
fi | |
if [ "$__init" -eq $TRUE ]; then | |
mkdir -p "$rpi_prj_root" || die "Could not create project folder." | |
if [ ! -e "$rpi_prj_rcfile" ]; then | |
rpi_prj_scriptfile="${rpi_prj_root}/${rpi_cfg_scriptfilename}" | |
println "$RPI_FILE_RPIRESOURCE" > "$rpi_prj_rcfile" || die "Could not create project's config-script file." | |
println "$RPI_FILE_RPIPROVISION" > "$rpi_prj_scriptfile" || die "Could not create project's provision script." | |
chmod a+x "$rpi_prj_scriptfile" || die "Could not set project's provision script as an all-executable." | |
fi | |
fi | |
} | |
_rpi_do_ensureprojectexists() { | |
if [ ! -e "$rpi_prj_rcfile" ]; then | |
_rpi_do_initvars "$TRUE" "${rpi_prj_root:-"${rpi_PWD}"}" | |
_rpi_do_prepareenv | |
_rpi_do_preparerc | |
fi | |
} | |
_rpi_do_download() { | |
local __url="$1"; shift | |
local __output="$1"; shift | |
"$RPI_CURL" -L -o "$__output" -C - "$__url" >&2 | |
} | |
_rpi_do_prepareenv() { | |
local __rcfile | |
local __root | |
local __stop | |
__stop="$FALSE" | |
rpi_run_stage="prepareenv" | |
if [ -z "$rpi_prj_root" ]; then | |
__root="${rpi_PWD}" | |
while [ "$__stop" -ne "$TRUE" ]; do | |
__rcfile="${__root}/${rpi_cfg_rcfilename}" | |
if [ -e "$__rcfile" ]; then | |
__stop="$TRUE" | |
debug "Project root found at '$__root'" | |
_rpi_do_initvars $FALSE "$__root" | |
fi | |
if [ "$__root" = "$(basename "$__root")" ]; then | |
__stop="$TRUE" | |
else | |
__root="$(basename "$__root")" | |
fi | |
done | |
fi | |
} | |
_rpi_do_preparerc() { | |
rpi_run_stage="rc" | |
[ ! "$rpi_run_stage" = "rc" ] && return | |
[ ! -e "$rpi_prj_rcfile" ] && return | |
debug "Including config-script file '$rpi_prj_rcfile'" | |
. "$rpi_prj_rcfile" | |
[ ! -z "$rpirc_zipfile" ] && rpi_prj_zipfile="${rpi_prj_root}/${rpirc_zipfile}" | |
[ ! -z "$rpirc_imgfile" ] && rpi_prj_imgfile="${rpi_prj_root}/${rpirc_imgfile}" | |
[ ! -z "$rpirc_provisioner" ] && rpi_prj_scriptfile="${rpi_prj_root}/${rpirc_provisioner}" | |
rpi_prj_rpiversion="${rpirc_piversion:-"$RPI_PIVERSION"}" | |
rpi_prj_rpimodel="${rpirc_pimodel:-"$RPI_PIMODEL"}" | |
rpi_prj_rpisoc="${rpirc_pisoc:-"$RPI_PISOC"}" | |
rpi_run_noautofix="${rpirc_noautofix:-"$FALSE"}" | |
rpi_prj_urlprefix="${rpirc_urlprefix:-"$RPI_IMAGE_URLPREFIX"}" | |
rpi_prj_distroversion="${rpi_prj_distroversion:-"$RPI_IMAGE_VERSION"}" | |
rpi_prj_distrovariant="${rpirc_distrovariant:-"$RPI_IMAGE_VARIANT"}" | |
rpi_prj_zipurl="${rpirc_zipurl:-"$(rpi_util_makedownloadurl)"}" | |
rpi_prj_zipfile="${rpi_prj_zipfile:-"${rpi_run_root}/raspbian-${rpi_prj_distrovariant}-${rpi_prj_distroversion}.zip"}" | |
rpi_prj_imgfile="${rpi_prj_imgfile:-"${rpi_run_root}/raspbian-${rpi_prj_distrovariant}-${rpi_prj_distroversion}.img"}" | |
rpi_prj_shell="${rpirc_shell:-"${RPI_GUEST_SHELL}"}" | |
rpi_prj_binfmtexec="${rpirc_binfmtexec:-"${RPI_BINFMTEXEC}"}" | |
if [ -z "$rpi_prj_rpisoc" ] && [ ! -z "$rpi_prj_rpiversion" ] && [ "$rpi_prj_rpiversion" -eq "3" ]; then | |
rpi_prj_rpisoc="bcm2710" | |
fi | |
# --- | |
rpi_run_bootdir="${rpi_run_root}/boot" | |
rpi_run_rpi3fixdir="${rpi_run_root}/rpi3-fix" | |
rpi_run_kernelfile="" | |
rpi_run_dtbfile="" | |
if [ ! -z "$rpi_prj_rpisoc" ] && [ ! -z "$rpi_prj_rpiversion" ] && [ ! -z "$rpi_prj_rpimodel" ]; then | |
rpi_run_kernelfile="$rpi_run_bootdir/kernel.img" | |
rpi_run_dtbfile="$rpi_run_bootdir/${rpi_prj_rpisoc}-rpi-${rpi_prj_rpiversion}-${rpi_prj_rpimodel}.dtb" | |
fi | |
rpi_run_rpi3kernelfile="$rpi_run_rpi3fixdir/kernel-qemu-$RPI_QEMUFIX_VERSION" | |
rpi_run_rpi3dtbfile="$rpi_run_rpi3fixdir/versatile-pb.dtb" | |
rpi_run_loopdevicecachefile="${rpi_run_envroot}/loop-device" | |
rpi_run_loopdevice="" | |
rpi_run_bootdevice="" | |
rpi_run_rootdevice="" | |
if [ -e "$rpi_run_loopdevicecachefile" ]; then | |
rpi_run_loopdevice="$(cat "$rpi_run_loopdevicecachefile")" || die "Could not read the loop device's name from cache file '$rpi_run_loopdevicecachefile'." | |
rpi_run_bootdevice="${rpi_run_loopdevice}p1" | |
rpi_run_rootdevice="${rpi_run_loopdevice}p2" | |
fi | |
} | |
_rpi_do_ensurefixqemu() { | |
local __qemu_major | |
local __qemu_minor | |
local __preload | |
# Shamelessly copied (and adapted) from: | |
# https://github.com/nachoparker/qemu-raspbian-network/blob/master/qemu-pi.sh#L129 | |
__qemu_major=$(qemu-system-arm --version | grep -oP '\d+\.\d+\.\d+' | head -1 | cut -d. -f1) | |
__qemu_minor=$(qemu-system-arm --version | grep -oP '\d+\.\d+\.\d+' | head -1 | cut -d. -f2) | |
if [ "$__qemu_major" -lt 2 ] || { [ "$__qemu_major" -eq 2 ] && [ "$__qemu_minor" -lt 8 ]; }; then | |
info "Implementing workarounds for running Raspbian on QEMU < 2.8..." | |
rpi_cmd_mount || die | |
__preload="$rpi_run_mntroot/etc/ld.so.preload" | |
$rpi_SUDO cp -f "$__preload" "${__preload}.bkp" >&2 || die "Could not make backup copy of '$__preload'!" | |
$rpi_SUDO sed -i '/^[^#].*libarmmem.so/s/^\(.*\)$/#\1/' "$__preload" >&2 || die "Could not change '$__preload'!" | |
if ! $rpi_SUDO sync "$__preload"; then | |
$rpi_SUDO mv -f -v "${__preload}.bkp" "${__preload}" >&2 | |
die "Could not sync the filesystem!" | |
fi | |
trap "$rpi_SUDO mv -f -v \"${__preload}.bkp\" \"${__preload}\" >&2" EXIT INT TERM || die "Could not register traps for undoing QEMU workarounds!" | |
fi | |
} | |
# --- | |
rpi_cmd_help() { | |
local __function | |
if [ "$#" -gt 0 ]; then | |
__function="rpi_cmd_${1}_help"; shift | |
if ! is_function "$__function"; then | |
warn "There is no help contents for command '$__function'." | |
else | |
"$__function" "$@" | |
fi | |
else | |
println "$RPI_NAME v$RPI_VERSION | |
$RPI_DESCRIPTION | |
USAGE: | |
$(basename "$0") [OPTIONS] <COMMAND> [COMMAND_ARGS...] | |
COMMANDS: | |
$(_rpi_do_listcmds) | |
" | |
fi | |
} | |
rpi_cmd_installdeps() { | |
local deps | |
local osid | |
local depsvar | |
osid="$(os_id)" || die "Could not retrieve the operating system's id/kind." | |
depsvar="RPI_DEPS_${osid}" | |
deps="$(eval "printf \"\$${depsvar}\"")" | |
info "Installing dependencies for OS '%s'" "$osid" | |
debug "Dependencies: %s" "$deps" | |
case "$osid" in | |
ubuntu|debian) exec $rpi_SUDO apt-get install -y $deps ;; | |
arch) exec "$RPI_INSTALLER_arch" -Sy --noconfirm $deps ;; | |
*) die "Unknown host operating system '$osid'. Please, install the dependencies yourself." ;; | |
esac | |
} | |
rpi_cmd_init() { | |
local __pwd | |
__pwd="$rpi_PWD" | |
if [ "$#" -gt 0 ]; then | |
__pwd="$1" && shift | |
fi | |
info "Initializing project in folder '%s'..." "$__pwd" | |
if [ -e "$__pwd/$rpi_cfg_rcfilename" ]; then | |
warn "There's already a project configured at '$__pwd'." | |
else | |
if [ -e "$__pwd/$rpi_cfg_rundirname" ]; then | |
warn "The project's root already has a run folder. Perhaps you should delete '$rpi_run_root' before proceeding if any error occurs." | |
fi | |
_rpi_do_initvars "$TRUE" "$__pwd" | |
fi | |
} | |
rpi_cmd_projdir_nosubshell="$TRUE" | |
rpi_cmd_projdir() { | |
if [ ! -z "$rpi_prj_root" ] && [ -e "$rpi_prj_root" ]; then | |
println "$rpi_prj_root" | |
else | |
debug "No project was found. Defaulting to PWD." | |
println "$rpi_PWD" | |
fi | |
} | |
rpi_cmd_mntdir_nosubshell="$TRUE" | |
rpi_cmd_mntdir() { | |
if [ ! -z "$rpi_run_mntroot" ] && [ -e "$rpi_run_mntroot" ]; then | |
println "$rpi_run_mntroot" | |
else | |
debug "Project was not mounted yet." | |
exit 1 | |
fi | |
} | |
rpi_cmd_configure_nosubshell="$TRUE" | |
rpi_cmd_configure() { | |
local __file | |
local __function | |
_rpi_do_ensureprojectexists | |
__function="rpirc_configure_pre" | |
if is_function "$__function"; then | |
"$__function" "$@" | |
fi | |
if [ ! -e "$rpi_run_root" ]; then | |
info "Creating runtime folder structure..." | |
mkdir -p "$rpi_run_root" || die "Could not create project's run folder." | |
mkdir -p "$rpi_run_mntroot" || die "Could not create project's chroot/mount folder." | |
mkdir -p "$rpi_run_envroot" || die "Could not create project's envvars folder." | |
mkdir -p "$rpi_run_imgroot" || die "Could not create project's downloads folder." | |
fi | |
if [ ! -e "$rpi_prj_imgfile" ]; then | |
if [ ! -e "$rpi_prj_zipfile" ]; then | |
info "Downloading image from '$rpi_prj_zipurl'..." | |
debug "Destination: $rpi_prj_zipfile" | |
_rpi_do_download "$rpi_prj_zipurl" "$rpi_prj_zipfile" || die "Could not download distribution image from URL '$rpi_prj_zipurl'!" | |
fi | |
__file="$("$RPI_UNZIP" -Z -1 "$rpi_prj_zipfile")" || die "Could not download distribution image ZIP from URL '$rpi_prj_zipurl'!" | |
[ "$(println "$__file" | wc -l)" -le 0 ] && die "Distribution image's ZIP file '$rpi_prj_zipfile' contains no files." | |
[ "$(println "$__file" | wc -l)" -gt 1 ] && die "Distribution image's ZIP file '$rpi_prj_zipfile' has more than one file. Please, customize image extraction via config-script." | |
info "Extracting image..." | |
"$RPI_UNZIP" "$rpi_prj_zipfile" "$__file" -d "$rpi_run_imgroot" >&2 | |
mv -v "${rpi_run_imgroot}/${__file}" "$rpi_prj_imgfile" >&2 || die "Could not move the extracted image." | |
fi | |
if [ ! -z "$rpi_prj_rpiversion" ] && [ "$rpi_prj_rpiversion" -eq 3 ]; then | |
if [ ! -e "$rpi_run_rpi3fixdir" ]; then | |
mkdir -p "$rpi_run_rpi3fixdir" || die "Could not create RPi 3 fixes' folder." | |
fi | |
if [ ! -e "$rpi_run_rpi3kernelfile" ]; then | |
info "Downloading fixed QEMU kernel '$rpi_run_rpi3kernelfile'..." | |
_rpi_do_download "$RPI_QEMUFIX_REPO/blob/master/$(basename "$rpi_run_rpi3kernelfile")?raw=true" "$rpi_run_rpi3kernelfile" || die "Could not download '$rpi_run_rpi3kernelfile'" | |
fi | |
if [ ! -e "$rpi_run_rpi3dtbfile" ]; then | |
info "Downloading fixed QEMU DTB '$rpi_run_rpi3dtbfile'..." | |
_rpi_do_download "$RPI_QEMUFIX_REPO/blob/master/$(basename "$rpi_run_rpi3dtbfile")?raw=true" "$rpi_run_rpi3dtbfile" || die "Could not download '$rpi_run_rpi3dtbfile'" | |
fi | |
fi | |
} | |
rpi_cmd_mount() { | |
[ ! -e "$rpi_prj_imgfile" ] && rpi_cmd_configure | |
if [ ! -e "$rpi_run_loopdevicecachefile" ]; then | |
rpi_run_loopdevice="$($rpi_SUDO losetup -f -P --show "$rpi_prj_imgfile")" || die "Could not allocate/create a loop device for image '$rpi_prj_imgfile'." | |
if ! (println "$rpi_run_loopdevice" > "$rpi_run_loopdevicecachefile"); then | |
$rpi_SUDO losetup -d "$rpi_run_loopdevice" | |
die "Could not store loop device's name in cache (at '$rpi_run_loopdevicecachefile')." | |
fi | |
rpi_run_bootdevice="${rpi_run_loopdevice}p1" | |
rpi_run_rootdevice="${rpi_run_loopdevice}p2" | |
fi | |
if ! { mount | grep "^$rpi_run_rootdevice"; } >/dev/null; then | |
info "Mounting the filesystem(s)..." | |
if [ -e "$rpi_run_mntroot" ]; then | |
[ ! -d "$rpi_run_mntroot" ] && die "Chroot destination is not a folder." | |
warn "This script expects to OWN the chroot directory at '$rpi_run_mntroot'. It will be EXCLUDED when a distclean is performed!" | |
fi | |
mkdir -p "$rpi_run_mntroot" || die "Could not create the chroot directory." | |
$rpi_SUDO mount "$rpi_run_rootdevice" -o rw "$rpi_run_mntroot" || die "Could not mount the RPi's root partition." | |
$rpi_SUDO mount "$rpi_run_bootdevice" -o rw "$rpi_run_mntroot/boot" || die "Could not mount the RPi's boot partition." | |
fi | |
if [ ! -e "$rpi_run_bootdir" ]; then | |
info "Copying the boot partition to a temporary folder..." | |
$rpi_SUDO cp -R "$rpi_run_mntroot/boot" "$rpi_run_bootdir" || die "Could not copy boot directory to temporary folder." | |
$rpi_SUDO chown -R "$(id -un):$(id -gn)" "$rpi_run_bootdir" || die "Could not recursively change ownership of the temporary boot folder." | |
fi | |
[ ! -e "$rpi_prj_binfmtexec" ] && die "Binfmt support is not installed! Be sure to provide a valid qemu-arm-static executable!" | |
if [ ! -e "$rpi_run_mntroot/usr/bin/$(basename "$rpi_prj_binfmtexec")" ]; then | |
info "Ensuring binfmt support is present..." | |
$rpi_SUDO cp -v "$rpi_prj_binfmtexec" "$rpi_run_mntroot/usr/bin" >&2 || die 1 "Could not copy '$rpi_prj_binfmtexec' to the chroot's bin folder!" | |
fi | |
} | |
rpi_cmd_expand() { | |
local __amount | |
local __device | |
[ "$#" -le 0 ] && die "Missing amount (in megabytes)." | |
__amount="$1"; shift | |
rpi_cmd_configure || die | |
rpi_cmd_umount || die "Could not unmount image." | |
qemu-img resize "$rpi_prj_imgfile" "+${__amount}M" >&2 || die "Could not grow the image file." | |
echo ", +" | sfdisk -N 2 "$rpi_prj_imgfile" || die "Could not grow the root partition." | |
__device="$($rpi_SUDO losetup -fP --show "$rpi_prj_imgfile")" || die "Could not allocate loop device." | |
$rpi_SUDO e2fsck -y -f "${__device}p2" | |
$rpi_SUDO resize2fs -f "${__device}p2" || die "Could not resize partition." | |
$rpi_SUDO losetup -d "$__device" | |
} | |
rpi_cmd_chroot() { | |
local __shell | |
[ ! -e "$rpi_run_loopdevicecachefile" ] && rpi_cmd_mount | |
__shell="$rpi_prj_shell" | |
[ "$#" -gt 0 ] && __shell="$1" && shift | |
info "Chrooting into system, executing shell '$__shell'..." | |
exec $rpi_SUDO systemd-nspawn -D "$rpi_run_mntroot" "bin/$__shell" "$@" | |
} | |
rpi_cmd_build_pre() { | |
local __userfunction | |
__userfunction="rpirc_build_pre" | |
if is_function "$__userfunction"; then | |
debug "Running pre-build user function at host..." | |
"$__userfunction" "$@" || die "The user's pre-build function failed!" | |
fi | |
} | |
rpi_cmd_build() { | |
local __tmpfile_host | |
local __tmpfile | |
local __userfunction | |
local __scriptfilemsg | |
__userfunction="rpirc_build" | |
__scriptfilemsg="" | |
[ -z "$rpi_prj_scriptfile" ] && __scriptfilemsg="This project has no provision script configured." | |
[ ! -e "$rpi_prj_scriptfile" ] && __scriptfilemsg="The provision script does not exist." | |
if is_function "$__userfunction" || [ -z "$__scriptfilemsg" ]; then | |
[ ! -e "$rpi_run_loopdevicecachefile" ] && rpi_cmd_mount | |
if [ -z "$__scriptfilemsg" ]; then | |
__tmpfile="/root/$(basename "$rpi_prj_scriptfile")" | |
__tmpfile_host="${rpi_run_mntroot}${__tmpfile}" | |
$rpi_SUDO mkdir -p "$rpi_run_mntroot/root" || die "Guest's '/root' folder does not exist!" | |
$rpi_SUDO cp -f "$rpi_prj_scriptfile" "$__tmpfile_host" || die "Could not copy provision script into host!" | |
$rpi_SUDO chmod a+x "$__tmpfile_host" || die "Could not set provision script permissions!" | |
fi | |
if is_function "$__userfunction"; then | |
debug "Running build user function at host..." | |
"$__userfunction" "$@" || die "The user's build function failed!" | |
fi | |
if [ -z "$__scriptfilemsg" ]; then | |
info "Running provisioning script..." | |
exec $rpi_SUDO systemd-nspawn -D "$rpi_run_mntroot" "bin/$rpi_prj_shell" "$__tmpfile" "$@" | |
fi | |
else | |
! is_function "$__userfunction" && error "The project has no host-side function/script to execute." | |
[ ! -z "$__scriptfilemsg" ] && error "$__scriptfilemsg" | |
die | |
fi | |
} | |
rpi_cmd_build_post() { | |
local __userfunction | |
__userfunction="rpirc_build_post" | |
if is_function "$__userfunction"; then | |
debug "Running post-build user function at host..." | |
"$__userfunction" "$@" || error "The user's post-build function failed!" | |
fi | |
} | |
rpi_cmd_cp() { | |
local __dest | |
if [ -z "$rpi_prj_imgfile" ] || [ ! -e "$rpi_prj_imgfile" ]; then | |
die "Image does not exist. Try to configure a project first." | |
fi | |
if [ "$#" -gt 0 ]; then | |
__dest="$1"; shift | |
else | |
__dest="$rpi_PWD" | |
fi | |
debug "Copying '$rpi_prj_imgfile' to '$__dest'..." | |
exec cp "$rpi_prj_imgfile" "$__dest" | |
debug "Finished copying '$rpi_prj_imgfile' to '$__dest'." | |
} | |
rpi_cmd_burn() { | |
local __device | |
local __confirm | |
[ "$#" -le 0 ] && die "Missing SD device name." | |
__device="$1" | |
if [ "$rpi_opt_confirmed" -ne "$TRUE" ]; then | |
printf "This is a destructive action: all data currently in '$__device' will be lost! Are you sure you want to proceed? [y/N]" >&2 | |
read __confirm | |
if [ "$__confirm" = "y" ] || [ "$__confirm" = "Y" ]; then | |
die "Aborted." | |
fi | |
fi | |
info "Burning image to device '$__device'..." | |
exec $rpi_SUDO dd bs=1M if="$rpi_prj_imgfile" of="$__device" | |
debug "Finished burning image to device '$__device'..." | |
} | |
rpi_cmd_emulate() { | |
local __exit | |
if [ ! -e "$rpi_prj_imgfile" ]; then | |
rpi_cmd_configure || die | |
fi | |
_rpi_do_ensurefixqemu | |
if [ ! "$rpi_run_noautofix" = "$TRUE" ] && [ ! -z "$rpi_prj_rpiversion" ] && [ "$rpi_prj_rpiversion" -eq 3 ]; then | |
debug "Using kernel file: $rpi_run_rpi3kernelfile" | |
debug "Using DTB file: $rpi_run_rpi3dtbfile" | |
info "Running QEMU..." | |
qemu-system-arm \ | |
-cpu arm1176 \ | |
-m 256 \ | |
-M versatilepb \ | |
-serial stdio \ | |
-kernel "$rpi_run_rpi3kernelfile" \ | |
-dtb "$rpi_run_rpi3dtbfile" \ | |
-drive "file=$rpi_prj_imgfile,format=raw" \ | |
-append "rw console=ttyAMA0 root=/dev/sda2 rootfstype=ext4 loglevel=8 rootwait fsck.repair=yes memtest=1 " \ | |
-net nic -net user,hostfwd=tcp::5022-:22 \ | |
-no-reboot | |
# -redir tcp:5022::22 \ | |
__exit="$?" | |
else | |
if [ -z "$rpi_run_kernelfile" ] || [ -z "$rpi_run_dtbfile" ]; then | |
die "Raspberry Pi's version and model not informed, cannot deduce kernel and DTB filenames." | |
fi | |
if [ ! -e "$rpi_run_kernelfile" ] || [ ! -e "$rpi_run_dtbfile" ]; then | |
rpi_cmd_mount || die | |
fi | |
debug "Using kernel file: $rpi_run_kernelfile" | |
debug "Using DTB file: $rpi_run_dtbfile" | |
if [ ! -e "$rpi_run_kernelfile" ] || [ ! -e "$rpi_run_dtbfile" ]; then | |
die "Could not find kernel/DTB files for (probably) invalid RPi model/version ${rpi_prj_rpiversion}-${rpi_prj_rpimodel}." | |
fi | |
info "Running QEMU..." | |
qemu-system-arm \ | |
-cpu arm1176 \ | |
-m 256 \ | |
-M versatilepb \ | |
-serial stdio \ | |
-kernel "$rpi_run_kernelfile" \ | |
-dtb "$rpi_run_dtbfile" \ | |
-drive "file=$rpi_prj_imgfile,format=raw" \ | |
-append "rw console=ttyAMA0 root=/dev/sda2 rootfstype=ext4 loglevel=8 rootwait fsck.repair=yes memtest=1" \ | |
-net nic -net user,hostfwd=tcp::5022-:22 \ | |
-no-reboot | |
# -redir tcp:5022::22 \ | |
__exit="$?" | |
fi | |
return "$__exit" | |
} | |
rpi_cmd_umount() { | |
local __lastcode | |
local __code | |
if [ -z "$rpi_prj_root" ] || [ ! -e "$rpi_prj_root" ]; then | |
die "The current folder is not part of a project." | |
fi | |
__code="0" | |
if { mount | grep "$rpi_run_mntroot/boot"; } >/dev/null; then | |
info "Unmounting '$rpi_run_mntroot/boot'..." | |
$rpi_SUDO umount "$rpi_run_mntroot/boot" >&2 | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
warn "Failed to unmount boot partition (at '$rpi_run_mntroot/boot')!" | |
__code="$__lastcode" | |
fi | |
fi | |
if { mount | grep "$rpi_run_mntroot"; } >/dev/null; then | |
info "Unmounting '$rpi_run_mntroot'..." | |
$rpi_SUDO umount "$rpi_run_mntroot" >&2 | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
warn "Failed to unmount root partition (at '$rpi_chroot_dir')!" | |
__code="$__lastcode" | |
fi | |
fi | |
if [ -e "$rpi_run_loopdevice" ]; then | |
if losetup -ln -O NAME | grep "$rpi_run_loopdevice" >/dev/null; then | |
$rpi_SUDO losetup -d "$rpi_run_loopdevice" >&2 | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
warn "Failed to delete loop device '$rpi_run_loopdevice'!" | |
__code="$__lastcode" | |
fi | |
fi | |
rm "$rpi_run_loopdevicecachefile" >&2 | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
warn "Failed to delete '$rpi_run_loopdevicecachefile'!" | |
__code="$__lastcode" | |
fi | |
fi | |
if [ -e "$rpi_run_bootdir" ]; then | |
rm -rf "$rpi_run_bootdir" >&2 | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
warn "Failed to delete temporary boot folder '$rpi_run_bootdir'!" | |
__code="$__lastcode" | |
fi | |
fi | |
return "$__code" | |
} | |
rpi_cmd_clean() { | |
local __lastcode | |
local __code | |
__code="0" | |
rpi_cmd_umount | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
__code="$__lastcode" | |
fi | |
rm -f "$rpi_prj_imgfile" | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
die "Could not delete image file '$rpi_prj_imgfile'." | |
__code="$__lastcode" | |
fi | |
return "$__code" | |
} | |
rpi_cmd_distclean() { | |
local __lastcode | |
local __code | |
__code="0" | |
rpi_cmd_clean | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
__code="$__lastcode" | |
fi | |
$rpi_SUDO rm -r "$rpi_run_root" | |
__lastcode="$?" | |
if [ "$__lastcode" -ne 0 ]; then | |
die "Could not delete run folder '$rpi_run_root'." | |
__code="$__lastcode" | |
fi | |
return "$__code" | |
} | |
# --- | |
rpi_main() { | |
local rpi_SUDO | |
local rpi_PWD | |
local rpi_SHIFT | |
rpi_PWD="$(pwd)" | |
if [ ! -z "$UID" ] && [ "$UID" -eq 0 ]; then | |
rpi_SUDO="env" | |
else | |
rpi_SUDO="sudo" | |
fi | |
local rpi_cfg_rundirname=".rpi" | |
local rpi_cfg_rcfilename="rpi.rc" | |
local rpi_cfg_scriptfilename="rpi-provision.sh" | |
local rpi_opt_confirmed="$FALSE" | |
local rpi_prj_shell | |
local rpi_prj_root | |
local rpi_prj_rcfile | |
local rpi_prj_scriptfile | |
local rpi_prj_urlprefix | |
local rpi_prj_distroversion | |
local rpi_prj_variant | |
local rpi_prj_zipurl | |
local rpi_prj_zipfile | |
local rpi_prj_imgfile | |
local rpi_prj_binfmtexec | |
local rpi_prj_rpiversion | |
local rpi_prj_rpimodel | |
local rpi_prj_rpisoc | |
local rpi_run_cmd | |
local rpi_run_stage | |
local rpi_run_phase | |
local rpi_run_root | |
local rpi_run_mntroot | |
local rpi_run_loopdevicecachefile | |
local rpi_run_noautofix | |
local rpi_run_kernelfile | |
local rpi_run_dtbfile | |
local rpi_run_rpi3kernelfile | |
local rpi_run_rpi3dtbfile | |
local rpi_run_bootdir | |
local rpi_run_rpi3fixdir | |
local rpi_run_loopdevice | |
local rpi_run_rootdevice | |
local rpi_run_bootdevice | |
local rpi_run_envroot # Folder containing environment variables' files, to be included | |
local rpi_run_imgroot # Folder to where the image is download and extracted | |
# --- | |
rpi_run_stage="parseopts" | |
rpi_SHIFT="0" | |
_rpi_do_parseopts "$@" || die "Could not parse arguments and initialize envvars!" | |
shift "$rpi_SHIFT" | |
rpi_run_stage="prepareenv" | |
_rpi_do_prepareenv | |
rpi_run_stage="rc" | |
_rpi_do_preparerc | |
rpi_run_stage="cmd" | |
[ "$#" -le 0 ] && die "Missing command." | |
rpi_run_cmd="$1"; shift | |
rpi_runcmd "$rpi_run_cmd" "$@" | |
} | |
rpi_main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment