#!/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 # 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 # This tool provides a generic way to install systemd based remote linux # initramfs. # Examples # -------- # Start install progress command (Assuming internet is available): # >>> ./build_initramfs.sh # Note that you only get very necessary output until you provide "--verbose" as # commandline options. ## region ensure presence of needed dependencies set -o errexit _needed_location="$(dirname "${BASH_SOURCE[0]}")/dnbd3-rootfs" if ! [[ -d "$_needed_location" ]]; then echo "The dnbd3 dracut plugin isn't available, loading it." if ! hash git; then echo "Needed dependency \"git\" isn't available. Please install \"git\" or provide the repositories data structure in \"$(dirname "${BASH_SOURCE[0]}")\"." fi _temporary_repository_location="$(mktemp --directory)" git clone git://git.openslx.org/openslx-ng/systemd-init.git \ "$_temporary_repository_location" pushd "$_temporary_repository_location" git submodule init git submodule update popd cp --recursive \ "${_temporary_repository_location}/builder/dnbd3-rootfs" \ "$_needed_location" rm --recursive --force "$_temporary_repository_location" fi set +o errexit ## endregion source "$(dirname "${BASH_SOURCE[0]}")/dnbd3-rootfs/scripts/rebash/core.sh" core.import exceptions exceptions.activate core.import logging core.import utils core.import change_root # endregion # region properties file_path='/boot/initramfs.img' dracut_parameter='--force --no-hostonly' verbose='no' debug='no' target='' create_system_image='' cleanup='no' use_systemd_in_initramfs='no' declare -A core_dependencies=( [cpio]='pack initramfs' \ [shift]='parse command line' \ [mktemp]='create save temporary files and dictionaries' \ [cat]='print messages' \ [rm]='remove (temporary) files' \ [sed]='process strings' \ [readlink]="connect dracut module with dracut's module system" \ [dirname]='core logic' \ [dmsetup]='create a (temporary) writable layer during boot' \ ['make gcc cmake']='dynamically compile needed resources against current or given kernel' \ [grep]='retrieve right boot partition during boot') declare -A optional_dependencies=( ['git gzip curl tar']='dynamically retrieve and unpack missing application which will be compiled for current or given kernel' \ [chroot]='build against a distribution other than this program runs in') declare -A core_shared_library_pattern_dependencies=( [libz]='compile dnbd3 for given or current kernel') declare -A optional_shared_library_pattern_dependencies=() declare -A core_package_dependencies=() declare -A optional_package_dependencies=( ['fuse glib-2.0 pixman-1']='support template systems in container (usually used by virtual runtime environments)') # endregion # region functions ## region command line interface print_usage_message() { # Prints a description about how to use this program. logging.cat << EOF This program provides a generic way to install systemd based remote linux initramfs. EOF } print_usage_examples() { # Prints a description about how to use this program by providing examples. logging.cat << EOF Start install progress: >>> ./build_initramfs.sh EOF } print_command_line_option_description() { # Prints descriptions about each available command line option. logging.cat << EOF -h --help Shows this help message. -v --verbose Tells you what is going on (default: "$verbose"). -d --debug Gives you any output from all tools which are used (default: "$debug"). -p --file-path Target location for initramfs file (default: "$file_path"). -c --cleanup Removes all distribution specific compiled files. -i --create-system-image Creates an image under given path from current system. (default: "$create_system_image"). -t --target Creates an image against given target template filesystem. If not explicitly speicifed current system will be used as template system (default: "$target"). -s --use-systemd-in-initramfs Use Systemd as init process in initramfs (improved performance but less features) (default: "$use_systemd_in_initramfs"). Additional dracut parameter and normal parameter can be added by delimiting them via a single dash (-) (default: "$dracut_parameter" with "--modules" and dynamically determined modules. Additional custom modules are concated.). EOF } print_help_message() { # Provides a help message for this module. logging.plain "\nUsage: $0 [options]\n" print_usage_message "$@" logging.plain '\nExamples:\n' print_usage_examples "$@" logging.plain -e '\nOption descriptions:\n' print_command_line_option_description "$@" logging.plain } parse_command_line() { # Provides the command line interface and interactive questions. while true; do case "$1" in -h|--help) shift print_help_message "$0" exit 0 ;; -v|--verbose) shift verbose='yes' ;; -d|--debug) shift debug='yes' ;; -p|--file-path) shift file_path="$1" if [[ "$file_path" == '' ]]; then logging.critical \ "This options needs a path to save initramfs image to." return 1 fi shift ;; -c|--cleanup) shift cleanup='yes' ;; -s|--use-systemd-in-initramfs) shift use_systemd_in_initramfs='yes' ;; -i|--create-system-image) shift create_system_image="$1" if [[ "$create_system_image" == '' ]]; then logging.critical \ "This options needs a path to save image to." return 1 fi shift ;; -t|--target) shift target="$1" if [[ "$target" == '' ]]; then logging.critical \ "This options needs a path create initramfs from." return 1 fi shift ;; -) shift while [[ "$1" =~ ^.+$ ]]; do dracut_parameter+=" $1" shift done shift ;; '') break ;; *) logging.critical "Given argument: \"$1\" is not available." return 1 esac done if [ "$verbose" == 'yes' ]; then logging.set_level info fi if [ "$debug" == 'yes' ]; then logging.set_level debug fi return 0 # TODO This sanity check is only needed for some cli combinations. if [[ "$UID" != '0' ]]; then logging.critical \ "You have to run this script as \"root\" not as \"${USER}\"." exit 2 fi return 0 } ## endregion ## region helper dependency_check() { # Check for given dependencies with given dependency checker and log # corresponding messages. local result=0 eval 'for dependency_group in "${!'$1'_'$2'[@]}"; do # NOTE: If "dependency_check_result" would be marked as local it is # empty later. dependency_check_result="$($3 $dependency_group)" || \ local return_code=$? if [[ $return_code == 1 ]]; then echo "$dependency_check_result" return $return_code elif [[ $return_code == 2 ]]; then while read dependency; do eval "local reason=\${${1}_${2}[\"\$dependency_group\"]}" local message="Missing $1 $4 dependency \"$dependency\" needed to $reason." if [[ $1 == core ]]; then logging.critical "$message" else logging.warn "$message" fi done <<< $dependency_check_result result=2 fi return_code=0 done' return $result } initialize_dracut() { # Downloads and compiles dracut. # # Examples: # # >>> initialize_dracut # ... mkdir --parents "$(dirname "${BASH_SOURCE[0]}")/dracut" logging.info 'Download and extract dracut.' curl --location \ https://www.kernel.org/pub/linux/utils/boot/dracut/dracut-043.tar.gz | \ tar --extract --gzip --directory \ "$(dirname "${BASH_SOURCE[0]}")/dracut" --strip-components 1 pushd "$(dirname "${BASH_SOURCE[0]}")/dracut" # NOTE: On virtualbox shared folder symlinks are not allowed. # NOTE: make the dracut-install binary (dracut-install resolves # dependencies etc.) logging.info 'Compiling dracut.' make install/dracut-install # NOTE: We have to copy the binary to current instead of symlinking them # since this feature isn't supported in shared virtual box machine folders. # If symlinks would be available we could simply use: # >>> make dracut-install cp "$(dirname "${BASH_SOURCE[0]}")/dracut/install/dracut-install" \ "$(dirname "${BASH_SOURCE[0]}")/dracut/dracut-install" popd return $? } cleanup() { # Removes distribution specific generated files. # # Examples: # # >>> cleanup local plugin_path="$(dirname "${BASH_SOURCE[0]}")/dnbd3-rootfs/" source "${plugin_path}module-setup.sh" moddir="$(cd "$plugin_path" &>/dev/null && pwd)" clean return $? } ## endregion # endregion # region controller result=0 dependency_check core dependencies utils_dependency_check program || \ result=$? dependency_check core shared_library_pattern_dependencies \ utils_dependency_check_shared_library 'shared library' || result=$? dependency_check core package_dependencies utils_dependency_check_pkgconfig \ package || result=$? [[ $result == 0 ]] || exit $result logging.set_commands_level debug logging.set_level debug if ! parse_command_line "$@"; then print_help_message "$0" exit 1 fi dependency_check optional dependencies utils_dependency_check program || \ result=$? dependency_check optional shared_library_pattern_dependencies \ utils_dependency_check_shared_library 'shared library' || result=$? dependency_check optional package_dependencies \ utils_dependency_check_pkgconfig package || result=$? [[ $result == 1 ]] && exit $result ## region handle delegated operations to specified target if [[ "$target" != '' ]]; then _target="$target" if [[ -f "$target" ]]; then _target="$(mktemp --directory)" _xmount_mountpoint_target="$(mktemp --directory)" xmount --in qemu "$target" --out raw \ "$_xmount_mountpoint_target" _xmount_device_target="$(losetup --find)" losetup "$_xmount_device_target" loopdev \ "${_xmount_mountpoint_target}/"*.dd mount "$_xmount_device_target" \ "$_target" fi if [[ -d "$_target" ]]; then _temporary_working_directory="$(chroot "$_target" mktemp --directory)" mount --bind "$(pwd)" "${_target}${_temporary_working_directory}" _parameter_skip=false _parameter_to_forward=() for _parameter; do if $_parameter_skip; then _parameter_skip=false elif [[ "$_parameter" == '-t' ]] || \ [[ "$_parameter" == '--target' ]] then _parameter_skip=true else _parameter_to_forward+=("$_parameter") fi done # NOTE: We would have to temporary patch dracut to avoid removing the # environment variables "LD_LIBRARY_PATH" and "LD_PRELOAD" to get # "fakechroot" working with dracut. So we should remove this variables # before running the patched dracut version to follow the initial # intention. You should first do: # >>> unset LD_LIBRARY_PATH # >>> unset LD_PRELOAD # and patch "dracut.sh" temporary to comment out: # >>> unset LD_LIBRARY_PATH # >>> unset LD_PRELOAD # To avoid to broke the "fakechroot" environment pipe the ldconfig call # to the native one: # >>> FAKECHROOT_CMD_SUBST=/usr/bin/ldconfig=/usr/bin/ldconfig change_root "${_target}" \ "${_temporary_working_directory}/${BASH_SOURCE[0]}" \ ${_parameter_to_forward[*]} mv "${_target}/$file_path" "$file_path" fi exit 0 fi ## endregion ## region handle dependencies which can be resolved automatically logging.info 'Checking dracut.' if ! [[ -f "$(dirname "${BASH_SOURCE[0]}")/dracut/dracut-install" ]]; then logging.info "Dracut isn't available yet loading it." initialize_dracut fi _dracut_modules_source='../../dnbd3-rootfs' _dracut_modules_target="$(dirname "${BASH_SOURCE[0]}")/dracut/modules.d/90dnbd3-rootfs" if [[ ! -L "$_dracut_modules_target" || "$(readlink \ "$_dracut_modules_target")" != "$_dracut_modules_source" ]] then logging.info \ "Link dnbd3 plugin into dracut modules folder ($_dracut_modules_source -> $_dracut_modules_target)." if ! ln --symbolic --force "$_dracut_modules_source" \ "$_dracut_modules_target" 2>/dev/null; then logging.warn \ "Link \"$_dracut_modules_source\" to \"$_dracut_modules_target\" fails. We will copy them. So we have to recopy it every time to ensure that recompiled things take effect." cp --recursive --force --no-target-directory \ "$(dirname "${BASH_SOURCE[0]}")/$(basename "$_dracut_modules_source")" \ "$_dracut_modules_target" fi fi ## endregion ## region prepare and perform final dracut call _loglevel='' if [ "$verbose" == 'yes' ]; then _loglevel='--verbose' fi _modules='dnbd3-rootfs' if [ "$debug" == 'yes' ]; then _loglevel="$_loglevel --stdlog 4" _modules="$_modules i18n terminfo" fi if [ "$use_systemd_in_initramfs" == 'yes' ]; then _modules="$_modules systemd systemd-initrd dracut-systemd" fi if [[ "$create_system_image" != '' ]]; then logging.info 'Create system image.' create_qcow2_system "$create_system_image" elif [[ "$cleanup" == 'yes' ]]; then logging.info 'Removing distribution specific files.' cleanup else logging.info 'Build initramfs.' # NOTE: We deactivate our exception handle since dracut returns "1" if it # is launched with help parameter ("-h" or "--help"). exceptions.deactivate "$(dirname "${BASH_SOURCE[0]}")/dracut/dracut.sh" --local \ $_loglevel --modules "$_modules" ${dracut_parameter[*]} "$file_path" exceptions.activate fi exceptions.deactivate ## endregion # endregion # region vim modline # vim: set tabstop=4 shiftwidth=4 expandtab: # vim: foldmethod=marker foldmarker=region,endregion: # endregion