Skip to content

Instantly share code, notes, and snippets.

@flisboac
Created May 1, 2019 00:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flisboac/5dc1984154f9fafb3d83c78503bcc4b5 to your computer and use it in GitHub Desktop.
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.
#!/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