blob: a59ebab006597b2a4052195e9c41f0ae1236b161 (
plain) (
tree)
|
|
#!/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.
declare -rg _root_dir="$(readlink -f $(dirname ${BASH_SOURCE[0]}))"
declare -rg _repo_dir="${_root_dir}/systemd-init.git"
declare -rg _git_source="git://git.openslx.org/openslx-ng/systemd-init.git"
declare -rg _git_branch="master"
## region ensure presence of needed dependencies
set -o errexit
if [ ! -e "$_repo_dir" ]; then
echo "Missing dracut modules repository, loading them."
if ! hash git; then
echo "Needed dependency \"git\" isn't available."
echo "Please install \"git\" or provide the main repository in \"${_repo_dir}\"."
fi
git clone --branch "$_git_branch" --single-branch --depth 1 \
"$_git_source" "${_repo_dir}"
pushd "${_repo_dir}"
git submodule init
# try to clone submodules as shallowy as possible, since we cannot just
# use '--depth 1' on submodules residing on non-master branches...
for mod in $(grep -Po '(?<=^\[submodule ")([^"]+)' .gitmodules); do
url="$(git config -f .gitmodules --get submodule.${mod}.url)"
path="$(git config -f .gitmodules --get submodule.${mod}.path)"
branch="$(git config -f .gitmodules --get submodule.${mod}.branch)"
commit="$(git submodule status $path | grep -oE '[0-9a-f]{40}')"
depth_arg=("--shallow-since")
[ "$mod" = "dnbd3" ] && depth_arg+=("2019-02-12")
[ "$mod" = "rebash" ] && depth_arg+=("2016-11-30")
[ "$mod" = "qemu-xmount" ] && depth_arg+=("2016-01-01")
[ "$mod" = "xmount" ] && depth_arg+=("2015-11-05")
[ "$mod" = "kernel-qcow2-linux" ] && depth_arg+=("2019-08-25")
[ "$mod" = "kernel-qcow2-util-linux" ] && depth_arg+=("2019-08-15")
git clone -n --no-tags "${depth_arg[@]}" --branch "$branch" "$url" "$path"
pushd "$path"
git checkout "$commit"
popd
done
# apply patches for submodules
git submodule foreach '
for p in $(find ${toplevel}/builder/patches/${path##*/} -type f -name "*.patch" | sort -n); do
patch -p1 < $p || echo "Failed to patch $path with $p - expect errors."
done 2>/dev/null
'
popd
fi
set +o errexit
## endregion
# shellcheck source=./dnbd3-rootfs/scripts/rebash/core.sh
source "${_repo_dir}/builder/modules.d/dnbd3-rootfs/scripts/rebash/core.sh"
core.import exceptions
core.import logging
core.import utils
core.import change_root
# endregion
# region properties
# shellcheck disable=SC2034
build_initramfs__doc_test_setup__='exceptions.activate'
file_path='/boot/initramfs.img'
dracut_parameter=(--force --no-hostonly)
verbose='no'
debug='no'
target=''
cleanup='no'
full_cleanup='no'
use_systemd_in_initramfs='no'
# shellcheck disable=SC2034
declare -A core_dependencies=(
[cat]='print messages' \
[cpio]='pack initramfs' \
['dhclient arping']='support network connection in resulting initramfs' \
[dirname]='core logic' \
[dmsetup]='create a (temporary) writable layer during boot' \
[grep]='retrieve right boot partition during boot' \
[mktemp]='create save temporary files and dictionaries' \
[pkg-config]='retrieve information of installed development packages' \
[tee]='read from standard input and write to standard output and files' \
[readlink]="connect dracut module with dracut's module system" \
[rm]='remove (temporary) files' \
[shift]='parse command line' \
[sed]='process strings' \
['cmake gcc make']='dynamically compile needed resources against current or given kernel')
# shellcheck disable=SC2034
declare -A optional_dependencies=(
[chroot]='build against a distribution other than this program runs in' \
['curl git gzip tar']='dynamically retrieve and unpack missing application which will be compiled for current or given kernel' \
['mkfs.ext4 fsck']='support for persistent binary diffs in image files')
# shellcheck disable=SC2034
declare -A core_shared_library_pattern_dependencies=(
[libz]='compile dnbd3 for given or current kernel')
# shellcheck disable=SC2034
declare -A optional_shared_library_pattern_dependencies=()
# shellcheck disable=SC2034
declare -A core_package_dependencies=()
# shellcheck disable=SC2034
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() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Prints a description about how to use this program.
>>> print_usage_message &>/dev/null
'
logging.cat << EOF
This program provides a generic way to install systemd based remote linux
initramfs.
EOF
}
print_usage_examples() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Prints a description about how to use this program by providing examples.
>>> print_usage_examples &>/dev/null
'
logging.cat << EOF
Start install progress:
>>> ./build_initramfs.sh
EOF
}
print_command_line_option_description() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Prints descriptions about each available command line option.
>>> print_command_line_option_description &>/dev/null
'
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.
-f --full-cleanup Removes all retrieved and compiled files (usefull to retrieve latest version of all nested modules).
-t --target Creates an image against given target template filesystem. If not
explicitly specified current system will be used as template system
(default: "$target").
-i --init Initializes the various repositories and build the required
dependencies but do not build the initramfs. Use this to accelerate
subsequent calls of this script.
-k --kernel-version Creates an image for the given kernel version. Will
require the presence of kernel headers for this version.
(default: "$(uname -r)")
-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
concatenated.).
EOF
}
print_help_message() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Provides a help message for this module.
>>> print_help_message &>/dev/null
'
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() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Provides the command line interface and interactive questions.
>>> parse_command_line -h &>/dev/null
>>> echo "$verbose"
no
>>> logging.get_level
critical
>>> parse_command_line -v
>>> echo "$verbose"
>>> logging.get_level
yes
info
>>> echo "$debug"
no
>>> parse_command_line --debug
>>> echo "$debug"
>>> logging.get_level
yes
debug
>>> parse_command_line -p
+doc_test_capture_stderr
+doc_test_contains
+doc_test_ellipsis
Error with given option "-p":
Traceback (most recent call first):
...
>>> echo "$file_path"
/boot/initramfs.img
>>> parse_command_line -p /tmp/test.img
>>> echo "$file_path"
/tmp/test.img
>>> echo "$cleanup"
no
>>> parse_command_line --cleanup
>>> echo "$cleanup"
yes
>>> echo "$full_cleanup"
no
>>> parse_command_line --full-cleanup
>>> echo "$full_cleanup"
yes
>>> echo "$full_cleanup"
no
>>> parse_command_line --full-cleanup
>>> echo "$full_cleanup"
yes
>>> echo "$use_systemd_in_initramfs"
no
>>> parse_command_line -s
>>> echo "$use_systemd_in_initramfs"
yes
>>> parse_command_line -t; echo $?
+doc_test_capture_stderr
+doc_test_contains
+doc_test_ellipsis
Error with given option "-t":
Traceback (most recent call first):
...
>>> [[ "$target" = "" ]]
>>> parse_command_line -t /tmp/
>>> echo "$target"
/tmp/
>>> echo "${dracut_parameter[@]}"
--force --no-hostonly
>>> parse_command_line - --test
>>> echo "${dracut_parameter[@]}"
--force --no-hostonly --test
>>> parse_command_line - --install "vim htop"
>>> echo "${dracut_parameter[3]}"
vim htop
>>> parse_command_line --no-available-option; echo $?
+doc_test_capture_stderr
+doc_test_contains
+doc_test_ellipsis
Error with given option "--no-available-option":
Traceback (most recent call first):
...
'
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)
local given_argument="$1"
shift
file_path="$1"
if [[ "$file_path" == '' ]]; then
logging.critical \
"Error with given option \"$given_argument\": This option needs a path to save initramfs image to."
return 1
fi
shift
;;
-c|--cleanup)
shift
cleanup='yes'
;;
-f|--full-cleanup)
shift
full_cleanup='yes'
;;
-s|--use-systemd-in-initramfs)
shift
use_systemd_in_initramfs='yes'
;;
-t|--target)
local given_argument="$1"
shift
target="$1"
if [[ "$target" == '' ]]; then
logging.critical \
"Error with given option \"$given_argument\": This option needs a path create initramfs from."
return 1
fi
shift
;;
-i|--init)
shift
initialize='yes'
;;
-k|--kernel-version)
local given_argument="$1"
shift
kernel_version="$1"
if [ -z "$kernel_version" ]; then
logging.critical \
"Error with given option \"$given_argument\": This option needs a kernel version to build the initramfs for."
return 1
fi
shift
;;
-H|--kernel-headers)
local given_argument="$1"
shift
kernel_headers="$1"
if [ -z "$kernel_headers" ]; then
logging.critical \
"Error with given option \"$given_argument\": This option needs the path to the kernel headers."
return 1
fi
shift
;;
-q|--qcow-handler)
local given_argument="$1"
shift
qcow_handler="$1"
if [ -z "$qcow_handler" ]; then
logging.critical \
"Error with given option \"$given_argument\": This options needs to be either 'xmount' or 'kernel'."
return 1
fi
shift
;;
-u|--update)
shift
update='yes'
;;
-)
shift
while [[ "$1" =~ ^.+$ ]]; do
dracut_parameter+=("$1")
shift
done
;;
'')
break
;;
*)
logging.critical \
"Error with given option \"$1\": This argument 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
# NOTE: Remove the following line if this sanity check should be performed.
return 0
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() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Check for given dependencies with given dependency checker and log
corresponding messages.
Example:
`dependency_check core dependencies utils_dependency_check program`
'
local result=0
# shellcheck disable=SC2016,SC1004
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() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Downloads and compiles dracut.
Example:
`initialize_dracut`
'
# First check what version to get
# Autodetect the kmod version present on the system to decide which dracut version to get
# * v47 requires kmod >= 23 (Available in Ubuntu 18.04)
# * v46 works with kmod == 20 (CentOS 7.5 only provides kmod v20)
if [ "$(pkg-config --modversion libkmod)" -ge 23 ]; then
dracut_version="047"
else
dracut_version="046"
fi
dracut_resource_url="https://www.kernel.org/pub/linux/utils/boot/dracut/dracut-$dracut_version.tar.gz"
if [[ ! -f "${_root_dir}/dracut/install/dracut-install" ]]; then
mkdir --parents "${_root_dir}/dracut"
logging.info "Download and extract dracut version $dracut_version"
curl --location "$dracut_resource_url" | tar --extract --gzip \
--directory "${_root_dir}/dracut" --strip-components 1
pushd "${_root_dir}/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.'
./configure
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
popd
fi
cp "${_root_dir}/dracut/install/dracut-install" \
"${_root_dir}/dracut/dracut-install"
return $?
}
cleanup() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Removes distribution specific generated files.
Example:
`cleanup`
'
local plugin_path
plugin_path="${_root_dir}/modules.d/dnbd3-rootfs"
# shellcheck disable=SC1090
source "${plugin_path}/module-setup.sh"
# shellcheck disable=SC2034
moddir="$(cd "$plugin_path" &>/dev/null && pwd)"
clean
return $?
}
## endregion
# endregion
# region controller
main() {
# shellcheck disable=SC2016,SC2034
local __doc__='
Main Entry point for the build initramfs logic. Triggers command line
parsing and calls sub routines depending on given command line arguments.
Example:
`main`
'
exceptions.activate
# region dependency checks and command line parsing
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 critical
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
# endregion
# region sanity check kernel version and qcow handler
# if no kernel was specified as dracut argument, use the running kernel's version
logging.info "Building for:"
if [ -z "$kernel_version" ]; then
kernel_version="$(uname -r)"
fi
logging.info " * 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
logging.critical 'Missing core dependency "linux-headers" for version to compile against given or current kernel.'
fi
logging.info " * kernel headers: $kernel_headers"
[ -n "$qcow_handler" ] && logging.info " * qcow2 handler: $qcow_handler"
# endregion
# region handle '--update' to update all the modules in 'modules.d'
if [ "$update" == "yes" ]; then
pushd "${_repo_dir}"
git pull
popd
fi
# endregion
# region handle dependencies which can be resolved automatically
logging.info 'Checking dracut.'
if [[ ! -f "${_root_dir}/dracut/dracut-install" ]]; then
logging.info "Dracut isn't available yet loading it."
initialize_dracut
fi
for _dracut_module_dir in "${_repo_dir}/builder/modules.d/"*; do
[ -d "${_dracut_module_dir}" ] || continue
_dracut_module="$(basename $_dracut_module_dir)"
# TODO allow module-specific priority
_dracut_module_target="${_root_dir}/dracut/modules.d/00${_dracut_module}"
if [[ ! -L "$_dracut_module_target" || "$(readlink \
"$_dracut_module_target")" != "$_dracut_module_dir" ]]; then
logging.info \
"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
logging.warn \
"Linking \"$_dracut_module_dir\" to \"$_dracut_module_target\" failed." \
" 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 \
"$_dracut_module_dir" \
"$_dracut_module_target"
fi
fi
done
# endregion
# region prepare modules and perform final dracut call
_loglevel=''
if [ "$verbose" == 'yes' ]; then
_loglevel='--verbose'
fi
_modules=(dnbd3-rootfs conf-tgz)
logging.info "Default modules: ${_modules[@]}"
if [ "$debug" == 'yes' ]; then
_loglevel="$_loglevel --stdlog 4"
_modules+=(i18n terminfo)
fi
if [ "$use_systemd_in_initramfs" == 'yes' ]; then
_modules+=(systemd systemd-initrd dracut-systemd)
fi
# Preprocess done - start build, cleanup or full cleanup
if [[ "$full_cleanup" == 'yes' ]]; then
logging.info 'Removing all modules.'
rm "${_repo_dir}" "${_root_dir}/dracut" --recursive --force
elif [[ "$cleanup" == 'yes' ]]; then
logging.info 'Removing distribution specific files.'
cleanup
else
. "${_repo_dir}/builder/modules.d/dnbd3-rootfs/helper/build.inc"
build_initialize_components
if [[ "$initialize" == 'yes' ]]; then
logging.info "Initialized."
exit 0
fi
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
# NOTE: We temporary allow dracut to forward all messages since we
# forward all logging configurations.
_commands_log_level_backup="$(logging.get_commands_level)"
_log_level_backup="$(logging.get_level)"
logging.set_level debug
logging.set_commands_level debug
# shellcheck disable=SC2086
"${_root_dir}/dracut/dracut.sh" --local \
$_loglevel --modules "${_modules[*]}" --conf /etc/dracut.conf \
--confdir /etc/dracut.conf.d "${dracut_parameter[@]}" \
--kver "${kernel_version}" "$file_path"
_return_code=$?
logging.set_commands_level "$_commands_log_level_backup"
logging.set_level "$_log_level_backup"
if [[ "$_return_code" != 0 ]]; then
logging.error 'Building initial ram file system failed.'
exit 1
fi
exceptions.activate
# NOTE: dracut generate the initramfs with 0600 permissions
chmod 0644 "${file_path}"
fi
# endregion
exceptions.deactivate
}
# endregion
if core.is_main; then
main "$@"
fi
# region vim modline
# vim: set tabstop=4 shiftwidth=4 expandtab:
# vim: foldmethod=marker foldmarker=region,endregion:
# endregion
|