blob: 188369e494c4572561711c6c4823ee08718c6e62 (
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
# 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:
# <root_dir>
# |- 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 '<module_name>.<key>=<value>[|<key>=<value>|...]'
if ! [[ "$1" =~ ^[A-Za-z0-9_]+\.[A-Za-z0-9]+=.+$ ]]; then
echo \
"Error with given option \"$given_argument\":" \
"'$1' not in expected format: <module_name>.<key>=<value>"
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 "$@"
|