#!/usr/bin/env bash # -*- coding: utf-8 -*- # region header # Copyright Torben Sickert (info["~at~"]torben.website) 29.10.2015 # Janosch Dobler (info["~at~"]jandob.com) 29.10.2015 # Jonathan Bauer (jonathan.bauer@rz.uni-freiburg.de) 19.09.2019 # Thiago Abdo (tjabdo@inf.ufpr.br) 06.11.2019 # License # ------- # This library written by Torben Sickert and Janosch Dobler stand under a # creative commons naming 3.0 unported license. # see http://creativecommons.org/licenses/by/3.0/deed.de ## endregion # Afaik the idea here was to have the root dir where build-initramfs.sh # is called from and the repo dir where the systemd-init.git is cloned to: # # |- build-initramfs.sh # |- dracut # |- systemd-init declare -rg _root_dir="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")" # TODO clean these up file_path='/boot/initramfs.img' verbose='no' debug='no' cleanup='no' full_cleanup='no' declare -rg _repo_dir="${_root_dir}/systemd-init" declare -rg _dracut_dir="${_root_dir}/dracut" # Autodetect the kmod version present on the system to decide which dracut version to get # * v59 requires kmod >= 23 (Available in Ubuntu 18.04) # * v46 works with kmod == 20 (CentOS 7.5 only provides kmod v20) declare -g _dracut_version="059" if [ "$(pkg-config --modversion libkmod)" -lt 23 ]; then _dracut_version="046" fi declare -A core_repo=( [handler]="git" [path]="$_repo_dir" [url]="https://git.openslx.org/openslx-ng/systemd-init.git" [branch]="master" ) declare -A core_dracut=( [handler]="http" [path]="$_dracut_dir" [url]="https://github.com/dracutdevs/dracut/archive/refs/tags/${_dracut_version}.tar.gz" ) declare -A module_dnbd3=( [handler]="git" [path]="${_repo_dir}/modules.d/dnbd3-rootfs/binaries/dnbd3" [url]="https://git.openslx.org/dnbd3.git" ) declare -A module_xloop=( [handler]="git" [path]="${_repo_dir}/modules.d/dnbd3-rootfs/binaries/xloop" [url]="git://git.openslx.org/openslx-ng/xloop.git" ) declare -A module_qemu_xmount=( [handler]="http" [path]="${_repo_dir}/modules.d/dnbd3-rootfs/binaries/qemu-xmount" [url]="https://github.com/eaas-framework/qemu/tarball/4873cd023da8511ed9792a318d1456c949046123" ) declare -A module_xmount=( [handler]="http" [path]="${_repo_dir}/modules.d/dnbd3-rootfs/binaries/xmount" [url]="https://github.com/eaas-framework/xmount/tarball/015137556fce1e21273f198ae0b9158157f74f74" ) declare -A override bootstrap() { for module in "core_repo" "core_dracut" "${!module_@}"; do declare -n _ref="$module" if [ $? -ne 0 ]; then echo "Only bash >= 4.3 supports namerefs." \ "You are running ${BASH_VERSION}." \ "Falling back to using evil eval..." eval 'declare -A _ref=(' \ '[handler]=${'"$module"'[handler]}' \ '[path]=${'"$module"'[path]}' \ '[url]=${'"$module"'[url]}' \ '[branch]=${'"$module"'[branch]}' \ '[commit]=${'"$module"'[commit]}' \ ')' # phew that was ugly.... fi if [ -n "$(ls -A "${_ref[path]}" 2> /dev/null)" ]; then echo "'${_ref[path]}' not empty, skipping..." continue fi echo "######################### $module #########################" if [ -n "${override["$module"]}" ]; then echo "Module overriden: ${override["$module"]}" IFS='|' read -r -a pairs <<< "${override["$module"]}" declare -p pairs for pair in "${pairs[@]}"; do IFS='=' read -r key value <<< "$pair" _ref["$key"]="$value" done fi echo "Handler: ${_ref[handler]}" echo " URL: ${_ref[url]}" echo " Branch: ${_ref[branch]}" echo " Commit: ${_ref[commit]}" echo " Path: ${_ref[path]}" # handlers deal with errors on their own handler_"${_ref[handler]}" \ "${_ref[path]}" \ "${_ref[url]}" \ "${_ref[branch]}" \ "${_ref[commit]}" # apply patches if any are required pushd "${_ref[path]}" || exit 1 for patch in "${_repo_dir}/patches/${_ref[path]##*/}/"*.patch; do [ -s "$patch" ] || continue if ! patch -p1 < "$patch"; then echo "Applying '$patch' failed, expecting errors. Continuing..." fi done popd || exit 1 done } handler_git() { if ! hash git 2>/dev/null; then echo "'git' binary not found, please install it and try again." exit 1 fi local path="$1" local url="$2" local branch="$3" local commit="$4" mkdir -p "$path" local gitargs=() if [ -n "$branch" ]; then gitargs+=("--branch" "$branch") fi if ! git clone "${gitargs[@]}" "$url" "$path"; then echo "Error cloning '$url' to '$path'." exit 1 fi [ -z "$commit" ] && return 0 # make sure given commit in in the fetched history local revision="$branch" if [ -n "$commit" ]; then revision="$commit" fi # manual "shallow clone" since not all server allow it... pushd "$path" || exit 1 local i=0 local last_count=0 while ! git rev-parse --quiet --verify "${revision}^{commit}"; do current_count="$(git rev-list --count --all)" if [ "$current_count" -eq "$last_count" ]; then echo "Failed to find '$revision' in '$2'." exit 1 fi last_count="$current_count" git fetch --depth=$(( i+=50 )) done git reset --hard "$revision" popd || exit 1 } handler_http() { if ! hash curl tar 2>/dev/null; then echo "'curl'/'tar' binaries not found, please install them and try again." exit 1 fi local path="$1" local url="$2" mkdir --parents "$path" set -o pipefail if ! curl \ --location \ --max-redirs 5 \ --max-time 60 \ --connect-timeout 5 \ --retry 3 \ --retry-max-time 120 \ "$url" \ | tar \ --extract \ --gzip \ --directory "$path" \ --strip-components 1; then echo "Failed to download/extract '$url' to '$path'." exit 1 fi } print_help_message() { echo "TODO" } parse_command_line() { while true; do case "$1" in -h|--help) print_help_message "$0" exit 0 ;; -v|--verbose) verbose='yes' ;; -d|--debug) debug='yes' ;; -p|--file-path) local given_argument="$1" shift file_path="$1" if [[ "$file_path" == '' ]]; then echo \ "Error with given option \"$given_argument\":" \ "This option needs a path to save initramfs image to." return 1 fi ;; -c|--cleanup) cleanup='yes' ;; -f|--full-cleanup) full_cleanup='yes' ;; -s|--use-systemd-in-initramfs) use_systemd_in_initramfs='yes' ;; -t|--target) local given_argument="$1" shift target="$1" if [[ "$target" == '' ]]; then echo \ "Error with given option \"$given_argument\":" \ "This option needs a path create initramfs from." return 1 fi ;; -i|--init) initialize='yes' ;; -k|--kernel-version) local given_argument="$1" shift kernel_version="$1" if [ -z "$kernel_version" ]; then echo \ "Error with given option \"$given_argument\":" \ "This option needs a kernel version to build the initramfs for." return 1 fi ;; -H|--kernel-headers) local given_argument="$1" shift kernel_headers="$1" if [ -z "$kernel_headers" ]; then echo \ "Error with given option \"$given_argument\":" \ "This option needs the path to the kernel headers." return 1 fi ;; -q|--qcow-handler) local given_argument="$1" shift qcow_handler="$1" ;; -u|--update) update='yes' ;; -O|--override-module) local given_argument="$1" shift # expects in format '.=[|=|...]' if ! [[ "$1" =~ ^[A-Za-z0-9_]+\.[A-Za-z0-9]+=.+$ ]]; then echo \ "Error with given option \"$given_argument\":" \ "'$1' not in expected format: .=" return 1 fi override_module="${1%%.*}" override_argument="${1#*.}" if [ -n "${override["$override_module"]}" ]; then # append to existing value override_argument="|${override_argument}" fi override["$override_module"]+="$override_argument" ;; -) shift dracut_parameter+=( "$@" ) break ;; *) if [ -n "$1" ]; then echo \ "Error with given option \"$1\": This argument is not available." return 1 else break fi ;; esac shift done if [ "$qcow_handler" = "xmount" ]; then unset module_xloop echo "Error: xmount support has been removed." return 1 elif [ "$qcow_handler" = "xloop" ]; then unset module_xmount else echo \ "Error with given option '--qcow-handler/-q':" \ "This option needs to be 'xloop', given: '$qcow_handler'." return 1 fi return 0 } ## endregion ## region helper initialize_dracut() { pushd "${_dracut_dir}" || exit 1 echo 'Compiling dracut.' ./configure || exit 1 make dracut-install || exit 1 make dracut-util # add a few handy functions to dracut if ! [ -s dracut-init.sh ]; then echo "no dracut-init.sh" exit 1 fi # One tab then 4 spaces, tab will get removed by -EOF cat >> dracut-init.sh <<-"EOF" slx_service() { local _name _desc _tmpfile declare -a _before=() _after=() _requires=() _wants=() _name="$1" _desc="$2" shift 2 while (( $# > 0 )); do case "$1" in --wbefore) _wants+=("$2") ;& --before) _before+=("$2") ;; --wafter) _wants+=("$2") ;& --after) _after+=("$2") ;; --wants) _wants+=("$2") ;; --requires) _requires+=("$2") ;; *) dfatal "Invalid option: '$1'" exit 10 esac shift 2 done if [ -z "$_desc" ]; then dfatal "Cannot install service without description" exit 1 fi mkdir --parents "${initdir}/usr/local/slx-services" _tmpfile="/tmp/dracut-service-$RANDOM" inst "$moddir/hooks/${_name}.sh" \ "/usr/local/slx-services/${_name}.sh" || exit 10 { echo "[Unit]" echo "Description=${_desc}" echo "DefaultDependencies=no" echo "Before=initrd-switch-root.target initrd-cleanup.service" [ -n "${_wants}" ] && echo "Wants=${_wants[*]}" [ -n "${_requires}" ] && echo "Requires=${_requires[*]}" [ -n "${_before}" ] && echo "Before=${_before[*]}" [ -n "${_after}" ] && echo "After=${_after[*]}" echo "" echo "[Service]" echo "Type=oneshot" echo "RemainAfterExit=yes" echo "ExecStart=/usr/local/slx-services/${_name}.sh" echo "KillMode=process" echo "KillSignal=SIGHUP" } > "${_tmpfile}" inst_simple "${_tmpfile}" \ "${systemdsystemunitdir}/${_name}.service" || exit 10 rm -f -- "${_tmpfile}" mkdir --parents \ "${initdir}/${systemdsystemunitdir}/initrd.target.wants" || exit 10 ln_r "${systemdsystemunitdir}/${_name}.service" \ "${systemdsystemunitdir}/initrd.target.wants/${_name}.service" || exit 10 } EOF popd || exit 1 return $? } # The idea here was to source each module-setup.sh from our # custom dracut modules and call their clean() function. # TODO: Does this still work? cleanup() { local plugin_path plugin_path="${_repo_dir}/modules.d/dnbd3-rootfs" source "${plugin_path}/module-setup.sh" moddir="$(cd "$plugin_path" &>/dev/null && pwd)" clean return $? } main() { if ! parse_command_line "$@"; then print_help_message "$0" exit 1 fi # prepare remote modules and set core variables. bootstrap if [ "$full_cleanup" = "yes" ]; then echo 'Removing all modules.' rm "${_repo_dir}" "${_dracut_dir}" --recursive --force exit $? fi if [ "$cleanup" = "yes" ]; then echo 'Removing distribution specific files.' cleanup exit $? fi # Default to the running kernel's version if none were specified echo "Building for:" if [ -z "$kernel_version" ]; then kernel_version="$(uname -r)" fi echo " * kernel version: $kernel_version" # similar for kernel headers needed to compile dnbd3 against. if [ -z "$kernel_headers" ]; then kernel_headers="/lib/modules/${kernel_version}/build" fi if [ ! -f "${kernel_headers}/Makefile" ]; then echo 'Missing kernel headers for given kernel!' exit 1 fi echo " * kernel headers: $kernel_headers" if [ -n "$qcow_handler" ]; then echo " * qcow2 handler: $qcow_handler" export qcow_handler fi if [ "$update" = "yes" ]; then pushd "${_repo_dir}" || exit 1 git pull popd || exit 1 fi echo 'Checking dracut...' if [ ! -f "${_dracut_dir}/dracut-install" ]; then echo "Dracut isn't available yet loading it." initialize_dracut fi # Newer versions want these to exist mkdir -p /etc/dracut.conf.d touch /etc/dracut.conf for _dracut_module_dir in "${_repo_dir}/modules.d/"*; do [ -d "${_dracut_module_dir}" ] || continue _dracut_module="$(basename "$_dracut_module_dir")" # TODO allow module-specific priority _dracut_module_target="${_dracut_dir}/modules.d/00${_dracut_module}" if [[ ! -L "$_dracut_module_target" || "$(readlink \ "$_dracut_module_target")" != "$_dracut_module_dir" ]]; then echo \ "Link ${_dracut_module} plugin into dracut modules folder" \ "($_dracut_module_dir -> $_dracut_module_target)." if ! ln --symbolic --force \ "$_dracut_module_dir" "$_dracut_module_target"; then echo \ "Failed to link: '$_dracut_module_dir' -> '$_dracut_module_target'." \ "Trying to copy them directly." if ! cp --recursive --force --no-target-directory \ "$_dracut_module_dir" "$_dracut_module_target"; then echo "Failed to copy them either, exiting." exit 1 fi fi fi done # default dracut parameters and modules dracut_parameter+=( --force --no-hostonly ) dracut_modules=( busybox dnbd3-rootfs conf-tgz haveged kexec-reboot systemd systemd-initrd dracut-systemd ) echo "Default modules: ${dracut_modules[*]}" if [ "$verbose" = "yes" ]; then dracut_parameters+=("--verbose") fi if [ "$debug" = "yes" ]; then dracut_parameters+=("--stdlog" "4") dracut_modules+=(i18n terminfo copy-initrd) fi # Initialize and exit if we are only preparing the build environment . "${_repo_dir}/modules.d/dnbd3-rootfs/helper/build.inc" if ! build_initialize_components; then echo 'Failed to build required components for dnbd3-rootfs.' exit 1 fi if [ "$initialize" = "yes" ]; then echo "Initialization succeeded." exit 0 fi # ask password shice wegkloppen sed -r -i 's/\S*systemd\S*-ask-password[.a-z0-9_/-]*//g' "${_dracut_dir}/modules.d/00systemd/module-setup.sh" echo 'Building initramfs...' "${_dracut_dir}/dracut.sh" --local \ --modules "${dracut_modules[*]}" --conf /etc/dracut.conf \ --confdir /etc/dracut.conf.d "${dracut_parameter[@]}" \ --kver "${kernel_version}" "$file_path" _return_code=$? if [ "$_return_code" != 0 ]; then echo 'Building initramfs failed.' exit 1 fi # NOTE: dracut generate the initramfs with 0600 permissions chmod 0644 "${file_path}" } main "$@"