qubes-linux-template-builder/functions.sh

432 lines
14 KiB
Bash
Executable File

#!/bin/bash
# vim: set ts=4 sw=4 sts=4 et :
set -e
VERBOSE=${VERBOSE:-1}
DEBUG=${DEBUG:-0}
################################################################################
# Global functions
################################################################################
# ------------------------------------------------------------------------------
# Define colors
# ------------------------------------------------------------------------------
colors() {
## Thanks to:
## http://mywiki.wooledge.org/BashFAQ/037
## Variables for terminal requests.
[[ -t 2 ]] && {
export alt=$( tput smcup || tput ti ) # Start alt display
export ealt=$( tput rmcup || tput te ) # End alt display
export hide=$( tput civis || tput vi ) # Hide cursor
export show=$( tput cnorm || tput ve ) # Show cursor
export save=$( tput sc ) # Save cursor
export load=$( tput rc ) # Load cursor
export bold=$( tput bold || tput md ) # Start bold
export stout=$( tput smso || tput so ) # Start stand-out
export estout=$( tput rmso || tput se ) # End stand-out
export under=$( tput smul || tput us ) # Start underline
export eunder=$( tput rmul || tput ue ) # End underline
export reset=$( tput sgr0 || tput me ) # Reset cursor
export blink=$( tput blink || tput mb ) # Start blinking
export italic=$( tput sitm || tput ZH ) # Start italic
export eitalic=$( tput ritm || tput ZR ) # End italic
[[ ${TERM} != *-m ]] && {
export red=$( tput setaf 1|| tput AF 1 )
export green=$( tput setaf 2|| tput AF 2 )
export yellow=$( tput setaf 3|| tput AF 3 )
export blue=$( tput setaf 4|| tput AF 4 )
export magenta=$( tput setaf 5|| tput AF 5 )
export cyan=$( tput setaf 6|| tput AF 6 )
}
export white=$( tput setaf 7|| tput AF 7 )
export default=$( tput op )
export eed=$( tput ed || tput cd ) # Erase to end of display
export eel=$( tput el || tput ce ) # Erase to end of line
export ebl=$( tput el1 || tput cb ) # Erase to beginning of line
export ewl=$eel$ebl # Erase whole line
export draw=$( tput -S <<< ' enacs
smacs
acsc
rmacs' || { \
tput eA; tput as;
tput ac; tput ae; } ) # Drawing characters
export back=$'\b'
} 2>/dev/null ||:
export build_already_defined_colors="true"
}
if [ ! "$build_already_defined_colors" = "true" ]; then
colors
fi
if [ "${VERBOSE}" -ge 2 -o "${DEBUG}" == "1" ]; then
chroot() {
local retval
true ${blue}
/usr/sbin/chroot "$@"
retval=$?
true ${reset}
return $retval
}
fi
# ------------------------------------------------------------------------------
# Display messages in color
# ------------------------------------------------------------------------------
# Only output text under certain conditions
output() {
if [ "${VERBOSE}" -ge 1 ]; then
# Don't echo if -x is set since it will already be displayed via true
[[ ${-/x} != $- ]] || echo -e "${1}"
fi
}
info() {
output "${bold}${blue}INFO: ${1}${reset}" || :
}
debug() {
output "${bold}${green}DEBUG: ${1}${reset}" || :
}
warn() {
output "${stout}${yellow}WARNING: ${1}${reset}" || :
}
error() {
output "${bold}${red}ERROR: ${1}${reset}" || :
}
# ------------------------------------------------------------------------------
# Takes an array and exports it a global variable
#
# $1: Array to export
# $2: Global variable name to use for export
#
# http://ihaveabackup.net/2012/01/29/a-workaround-for-passing-arrays-in-bash/
#
# ------------------------------------------------------------------------------
setArrayAsGlobal() {
local array="$1"
local export_as="$2"
local code=$(declare -p "$array")
local replaced="${code/$array/$export_as}"
eval ${replaced/declare -/declare -g}
}
# ------------------------------------------------------------------------------
# Spilts the path and returns an array of parts
#
# $1: Full path of file to split
# $2: Global variable name to use for export
# Returns:
# ([full]='original name' [dir]='directory' [base]='filename' [ext]='extension')
#
# Original concept path split from:
# https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash
#
# ------------------------------------------------------------------------------
splitPath() {
local return_global_var=$2
local filename="${1##*/}" # Strip longest match of */ from start
local dir="${1:0:${#1} - ${#filename}}" # Substring from 0 thru pos of filename
local base="${filename%.[^.]*}" # Strip shortest match of . plus at least one non-dot char from end
local ext="${filename:${#base} + 1}" # Substring from len of base thru end
if [ "$ext" ]; then
local dotext=".$ext"
else
local dotext=""
fi
if [[ -z "$base" && -n "$ext" ]]; then # If we have an extension and no base, it's really the base
base=".$ext"
ext=""
dotext=""
fi
declare -A PARTS=([full]="$1" [dir]="$dir" [base]="$base" [ext]="$ext" [dotext]="$dotext")
setArrayAsGlobal PARTS $return_global_var
}
templateFlavor() {
echo ${TEMPLATE_FLAVOR}
}
templateFlavorPrefix() {
local template_flavor=${1-$(templateFlavor)}
# If TEMPLATE_FLAVOR_PREFIX is not already an array, make it one
if ! [[ "$(declare -p TEMPLATE_FLAVOR_PREFIX 2>/dev/null)" =~ ^declare\ -a.* ]] ; then
TEMPLATE_FLAVOR_PREFIX=( ${TEMPLATE_FLAVOR_PREFIX} )
fi
for element in "${TEMPLATE_FLAVOR_PREFIX[@]}"
do
if [ "${element%:*}" == "${DIST}+${template_flavor}" ]; then
echo ${element#*:}
return
fi
done
echo "${DIST}${template_flavor:++}"
}
templateNameDist() {
local dist_name="${1}"
template_name="$(templateName)" && dist_name="${template_name}"
# XXX: Temp hack to shorten name
if [ ${#dist_name} -ge 32 ]; then
if [ ${#template_name} -lt 32 ]; then
dist_name="${template_name}"
else
dist_name="${dist_name:0:31}"
fi
fi
# Remove and '+' characters from name since they are invalid for name
dist_name="${dist_name//+/-}"
echo ${dist_name}
}
templateName() {
local template_flavor=${1-$(templateFlavor)}
retval=1 # Default is 1; mean no replace happened
# Only apply options if $1 was not passed
if [ -n "${1}" ]; then
local template_options=
else
local template_options="${TEMPLATE_OPTIONS// /+}"
fi
local template_name="$(templateFlavorPrefix ${template_flavor})${template_flavor}${template_options:++}${template_options}"
# If TEMPLATE_LABEL is not already an array, make it one
if ! [[ "$(declare -p TEMPLATE_LABEL 2>/dev/null)" =~ ^declare\ -a.* ]] ; then
TEMPLATE_LABEL=( ${TEMPLATE_LABEL} )
fi
for element in "${TEMPLATE_LABEL[@]}"; do
if [ "${element%:*}" == "${template_name}" ]; then
template_name="${element#*:}"
retval=0
break
fi
done
if [ ${#template_name} -ge 32 ]; then
error "Template name is greater than 31 characters: ${template_name}"
error "Please set an alias"
error "Exiting!!!"
exit 1
fi
echo ${template_name}
return $retval
}
templateDir() {
local template_flavor=${1-$(templateFlavor)}
# If TEMPLATE_FLAVOR_DIR is not already an array, make it one
if ! [[ "$(declare -p TEMPLATE_FLAVOR_DIR 2>/dev/null)" =~ ^declare\ -a.* ]] ; then
TEMPLATE_FLAVOR_DIR=( ${TEMPLATE_FLAVOR_DIR} )
fi
for element in "${TEMPLATE_FLAVOR_DIR[@]}"
do
# (wheezy+whonix-gateway / wheezy+whonix-gateway+gnome[+++] / wheezy+gnome )
if [ "${element%:*}" == "$(templateName ${template_flavor})" ]; then
eval echo -e ${element#*:}
return
# Very short name compare (+proxy)
elif [ "${element:0:1}" == "+" -a "${element%:*}" == "+${template_flavor}" ]; then
eval echo -e ${element#*:}
return
fi
done
if [ -n "${template_flavor}" ]; then
local template_flavor_prefix="$(templateFlavorPrefix ${template_flavor})"
local dir="${SCRIPTSDIR}/${template_flavor_prefix}${template_flavor}"
else
local dir="${SCRIPTSDIR}"
fi
echo "${dir}"
}
templateFile() {
local file="$1"
local suffix="$2"
local template_flavor="$3"
local template_dir="$(templateDir "${template_flavor}")"
splitPath "${file}" path_parts
# Append suffix to filename (before extension)
if [ "${suffix}" ]; then
file="${template_dir}/${path_parts[base]}_${suffix}${path_parts[dotext]}"
else
file="${template_dir}/${path_parts[base]}${path_parts[dotext]}"
fi
if [ -f "${file}" ]; then
echo "${file}"
fi
}
buildStepExec() {
local filename="$1"
local suffix="$2"
local template_flavor="$3"
script="$(templateFile "${filename}" "${suffix}" "${template_flavor}")"
if [ -f "${script}" ] && [ ! ${GLOBAL_CACHE[$script]+_} ]; then
# Test module expects raw output back only used to asser test results
if [[ -n ${TEST} ]]; then
echo "${script}"
else
output "${bold}${under}INFO: Currently running script: ${script}${reset}"
fi
# Cache $script
GLOBAL_CACHE[$script]=1
# Execute $script
"${script}"
fi
}
copyTreeExec() {
local source_dir="$1"
local dir="$2"
local template_flavor="$3"
local target_dir="$4"
local template_dir="$(templateDir ${template_flavor})"
local source_dir="$(readlink -m ${source_dir:-${template_dir}}/${dir})"
local target_dir="$(readlink -m ${target_dir:-${INSTALLDIR}})"
if ! [ -d "${source_dir}" ]; then
debug "No extra files to copy for ${dir}"
return 0
fi
debug "Copying ${source_dir}/* ${target_dir}"
cp -rp "${source_dir}/." "${target_dir}"
if [ -f "${source_dir}/.facl" ]; then
debug "Restoring file permissions..."
pushd "${target_dir}"
{
setfacl --restore="${source_dir}/.facl" 2>/dev/null ||:
rm -f .facl
}
popd
fi
}
callTemplateFunction() {
# Reset Cache
unset GLOBAL_CACHE
declare -A -g GLOBAL_CACHE
local calling_script="$1"
local calling_arg="$2"
local functionExec="$3"
local template_flavor="$(templateFlavor)"
${functionExec} "${calling_script}" \
"${calling_arg}" \
"${template_flavor}"
for option in ${TEMPLATE_OPTIONS[@]}
do
# Long name (wheezy+whonix-gateway+proxy)
${functionExec} "${calling_script}" \
"${calling_arg}" \
"$(templateFlavor)+${option}"
# Short name (wheezy+proxy)
${functionExec} "${calling_script}" \
"${calling_arg}" \
"${option}"
done
# If template_flavor exists, also check on base distro
if [ -n "${template_flavor}" ]; then
${functionExec} "${calling_script}" \
"${calling_arg}"
fi
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
getFileLocations() {
local return_global_var=$1
local filename="$2"
local suffix="$3"
local function="templateFile"
files="$(callTemplateFunction "${filename}" "${suffix}" "${function}")"
IFS_orig="${IFS}}"; IFS=$'\n'
files=( "${files}" )
setArrayAsGlobal files $return_global_var
IFS="${IFS_orig}"
}
# ------------------------------------------------------------------------------
# Executes any additional optional configuration steps if the configuration
# scripts exist
# ------------------------------------------------------------------------------
buildStep() {
local filename="$1"
local suffix="$2"
local function="buildStepExec"
callTemplateFunction "${filename}" "${suffix}" "${function}"
}
# ------------------------------------------------------------------------------
# Copy extra file tree to ${INSTALLDIR}
# TODO: Allow copy per step (04_install_qubes.sh-files)
#
# To set file permissions is a PITA since git won't save them and will
# complain heavily if they are set to root only read, so this is the procdure:
#
# 1. Change to the directory that you want to have file permissions retained
# 2. Change all the file permissions / ownership as you want
# 3. Change back to the root of the exta directory (IE: extra-qubes-files)
# 4. Manually restore facl's: setfacl --restore=.facl
# 5. Manually create facl backup used after copying: getfacl -R . > .facl
# 6. If git complains; reset file ownership back to user. The .facl file stored
# the file permissions and will be used to reset the file permissions after
# they get copied over to ${INSTALLDIR}
# NOTE: Don't forget to redo this process if you add -OR- remove files
# ------------------------------------------------------------------------------
copyTree() {
local dir="$1"
local source_dir="$2"
local target_dir="$3"
local function="copyTreeExec"
if [ "x${source_dir}" == "x" ]; then
callTemplateFunction "" "${dir}" "${function}"
else
copyTreeExec "${source_dir}" "${dir}" "" "${target_dir}"
fi
}
# $0 is module that sourced vars.sh
output "${bold}${under}INFO: Currently running script: ${0}${reset}"