From: Reto Gantenbein Date: Sat, 31 Dec 2016 00:43:10 +0000 (+0100) Subject: Complete rework of lxc-fedora template X-Git-Tag: lxc-2.1.0~197^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3d7aa7887ac4ff1a1b01bdfcb5954688b7884c1b;p=thirdparty%2Flxc.git Complete rework of lxc-fedora template Heavily refactored version of the Fedora container creation template. Removed compatibility to unsupported Fedora releases including sysvinit and yum stuff. Added new commandline arguments found in other templates: --mirror : To set custom HTTP(s) Fedora download mirror --packages : List of custom packages to install into a new container --debug : Run with shell script with 'set -x' There are also some new environment variables which can be used to customize the template behaviour. See --help output. Supports Fedora >=24. By default Fedora 25 will be installed except on Fedora hosts, where the host release is taken. To simplify the code path (or at least not make it more complex) all non-Fedora hosts will now use the LiveOS-image based bootstrap environment even when they would natively support rpm, yum or even dnf (e.g. Gentoo, CentOS). Mainly runs systemd services by default (journald, networkd, resolved logind). Signed-off-by: Reto Gantenbein --- diff --git a/templates/Makefile.am b/templates/Makefile.am index 6d41a2a54..69cd80ebf 100644 --- a/templates/Makefile.am +++ b/templates/Makefile.am @@ -9,6 +9,7 @@ templates_SCRIPTS = \ lxc-cirros \ lxc-debian \ lxc-download \ + lxc-fedora \ lxc-fedora-legacy \ lxc-gentoo \ lxc-openmandriva \ diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in new file mode 100644 index 000000000..926cb4867 --- /dev/null +++ b/templates/lxc-fedora.in @@ -0,0 +1,1241 @@ +#!/bin/bash + +# +# template script for generating Fedora container for LXC +# + +# +# lxc: linux Container library + +# Authors: +# Daniel Lezcano +# Ramez Hanna +# Michael H. Warfield +# Reto Gantenbein + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. + +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# configurations +FEDORA_RELEASE_MIN=24 +FEDORA_RELEASE_DEFAULT=${FEDORA_RELEASE_DEFAULT:-25} +FEDORA_RSYNC_URL="${FEDORA_RSYNC_URL:-archives.fedoraproject.org::fedora-enchilada}" +MIRRORLIST_URL=${MIRRORLIST_URL:-http://mirrors.fedoraproject.org/mirrorlist} + +local_state_dir="@LOCALSTATEDIR@" +lxc_path="@LXCPATH@" +lxc_template_config="@LXCTEMPLATECONFIG@" +lxc_default_conf="@LXC_DEFAULT_CONFIG@" + +# allows the cache directory to be set by environment variable +LXC_CACHE_PATH="${LXC_CACHE_PATH:-"${local_state_dir}/cache/lxc"}" + +# these are only going into comments in the resulting config... +lxc_network_type=veth +lxc_network_link=lxcbr0 + +# Some combinations of the tuning knobs below do not exactly make sense. +# but that's ok. +# +# If the "root_password" is non-blank, use it, else set a default. +# This can be passed to the script as an environment variable and is +# set by a shell conditional assignment. Looks weird but it is what it is. +# +# If the root password contains a ding ($) then try to expand it. +# That will pick up things like ${name} and ${RANDOM}. +# If the root password contains more than 3 consecutive X's, pass it as +# a template to mktemp and take the result. +# +# If root_display_password = yes, display the temporary root password at exit. +# If root_store_password = yes, store it in the configuration directory +# If root_prompt_password = yes, invoke "passwd" to force the user to change +# the root password after the container is created. +# If root_expire_password = yes, you will be prompted to change the root +# password at the first login. +# +# These are conditional assignments... The can be overridden from the +# preexisting environment variables... +# +# Make sure this is in single quotes to defer expansion to later! +# :{root_password='Root-${name}-${RANDOM}'} +: "${root_password='Root-${name}-XXXXXX'}" + +# Now, it doesn't make much sense to display, store, and force change +# together. But, we gotta test, right??? +: "${root_display_password='no'}" +: "${root_store_password='yes'}" +# Prompting for something interactive has potential for mayhem +# with users running under the API... Don't default to "yes" +: "${root_prompt_password='no'}" + +# Expire root password? Default to yes, but can be overridden from +# the environment variable +: "${root_expire_password='yes'}" + +# detect use under userns (unsupported) +for arg in "$@"; do + [ "${arg}" = "--" ] && break + if [ "${arg}" = "--mapped-uid" ] || [ "${arg}" = "--mapped-gid" ] + then + echo "This template can't be used for unprivileged containers." 1>&2 + echo "You may want to try the \"download\" template instead." 1>&2 + exit 1 + fi +done + +# make sure the usual locations are in PATH +export PATH=${PATH}:/usr/sbin:/usr/bin:/sbin:/bin + +# dnf package manager arguments +dnf_args=( --assumeyes --best --allowerasing --disablerepo=* --enablerepo=fedora --enablerepo=updates ) + +# This function is going to setup a minimal Fedora bootstrap environment +# which will be used to create new containers on non-Fedora hosts. +# +bootstrap_fedora() +{ + local cache="${1}" + + local bootstrap="${cache}/bootstrap" + if [ -d "${bootstrap}" ] + then + echo "Existing Fedora bootstrap environment found. Testing ..." + if chroot_update_fedora "${bootstrap}" + then + CHROOT_DIR="${bootstrap}" + CHROOT_CMD="chroot ${CHROOT_DIR} " + + echo "Bootstrap environment appears to be functional." + return 0 + else + echo "Error: Bootstrap environment detected in ${bootstrap}" + echo "but appears to be non-functional. Please remove and restart." + return 1 + fi + fi + + echo "Setting up new Fedora ${FEDORA_RELEASE_DEFAULT} (${arch}) bootstrap environment." + + [[ -d "${cache}" ]] || mkdir -p "${cache}" + + tmp_bootstrap_dir=$( mktemp -d --tmpdir="${cache}" bootstrap_XXXXXX ) + pushd "${tmp_bootstrap_dir}" >/dev/null + + mkdir squashfs liveos bootstrap + + # download the LiveOS squashfs image + if [ ! -f "${cache}/install.img" ] + then + + local image_path="/linux/releases/${FEDORA_RELEASE_DEFAULT}/Everything/${arch}/os/images/install.img" + local ret=1 + + if [ -n "${mirror}" ] + then + echo -n "Downloading LiveOS squashfs image from ${mirror} ... " + curl --silent --show-error --fail --remote-name "${mirror}${image_path}" + ret=$? + echo + else + echo "Syncing LiveOS squashfs image from ${FEDORA_RSYNC_URL} ... " + rsync --archive --info=progress "${FEDORA_RSYNC_URL}${image_path}" . + ret=$? + fi + if [ "${ret}" != 0 ] || [ ! -s install.img ] + then + echo "Error: Download of squashfs image failed." + return 1 + fi + mv install.img "${cache}/" + else + echo "Using cached LiveOS squashfs image." + fi + + echo "Mounting LiveOS squashfs file system." + if ! mount -o loop "${cache}/install.img" squashfs/ + then + echo " +Error: Mount of LiveOS squashfs image failed +-------------------------------------------- +You must have squashfs support available to mount image. LiveOS image is now +cached. Process may be rerun without penalty of downloading LiveOS again. If +LiveOS is corrupt, remove ${cache}/install.img +" + return 1 + fi + + # mount contained LiveOS + if ! mount -o loop squashfs/LiveOS/rootfs.img liveos + then + echo " +Error: Mount of LiveOS stage0 rootfs image failed +------------------------------------------------- +LiveOS download may be corrupt. Remove ${cache}/LiveOS +to force a new download. +" + return 1 + fi + + echo "Copying LiveOS content to bootstrap environment ... " + if ! rsync --archive --acls --hard-links --sparse liveos/. bootstrap/ + then + echo "Error: Build of bootstrap environment failed." + echo "Keeping directory ${tmp_bootstrap_dir} for your investigation." + exit 1 + fi + + # unmount liveos mounts - we're done with liveos at this point + umount liveos + umount squashfs + + # customize bootstrap rootfs + pushd bootstrap >/dev/null + + # setup repositories in bootstrap chroot + CHROOT_DIR="$(pwd)" + CHROOT_CMD="chroot ${CHROOT_DIR} " + INSTALL_ROOT="/" + if ! setup_repositories "${cache}" "${arch}" "${FEDORA_RELEASE_DEFAULT}" "${mirror}" + then + echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}" + exit 1 + fi + if [ -n "${mirror}" ] + then + set_dnf_mirror_url ./etc/yum.repos.d/fedora*.repo + fi + + # make sure /etc/resolv.conf is up to date in the chroot + cp /etc/resolv.conf ./etc/ + + # verify bootstrap chroot by running a dnf update + chroot_update_fedora "$(pwd)" + ret=$? + + popd >/dev/null + + if [ "${ret}" != 0 ] + then + echo "Error: Build of bootstrap environment failed." + echo "Keeping directory ${tmp_bootstrap_dir} for your investigation." + return 1 + fi + + mv bootstrap "${cache}" + + popd >/dev/null + rm -rf "${tmp_bootstrap_dir}" + + CHROOT_DIR="${bootstrap}" + CHROOT_CMD="chroot ${CHROOT_DIR} " + + echo "Fedora bootstrap environment successfully created." + return 0 +} + +chroot_mounts() +{ + test -n "${1}" && local chroot="${1}" || return 1 + + mount -t proc proc "${chroot}/proc" && + mount -o bind /dev "${chroot}/dev" +} + +chroot_umounts() +{ + test -n "${1}" && local chroot="${1}" || return 1 + + umount "${chroot}/proc" && + umount "${chroot}/dev" +} + +clean_cache() +{ + local cache="${1}" + + test ! -e "${cache}" && return 0 + + # lock, so we won't purge while someone is creating a repository + ( + if ! flock -x 9 + then + echo "Error: Cache repository is busy." + exit 1 + fi + + echo -n "Purging the Fedora bootstrap and download cache ... " + rm --preserve-root --one-file-system -rf "${cache}" && echo "Done." || exit 1 + + exit 0 + + ) 9>"${local_state_dir}/lock/subsys/lxc-fedora" + + return $? +} + +# Customize container rootfs +# +configure_fedora() +{ + local rootfs="${1}" + local release="${2}" + local utsname="${3}" + + # disable selinux + mkdir -p "${rootfs}/selinux" + echo 0 > "${rootfs}/selinux/enforce" + + # also kill it in the /etc/selinux/config file if it's there... + if [ -f "${rootfs}/etc/selinux/config" ] + then + sed -i '/^SELINUX=/s/.*/SELINUX=disabled/' "${rootfs}/etc/selinux/config" + fi + + # nice catch from Dwight Engen in the Oracle template. + # wantonly plagerized here with much appreciation. + if [ -f "${rootfs}/usr/sbin/selinuxenabled" ] + then + rm -f "${rootfs}/usr/sbin/selinuxenabled" + ln -s /bin/false "${rootfs}/usr/sbin/selinuxenabled" + fi + + # set hostname + echo "${utsname}" > "${rootfs}/etc/hostname" + + # set default localtime to the host localtime if not set... + if [ -e /etc/localtime ] && [ ! -e "${rootfs}/etc/localtime" ] + then + # if /etc/localtime is a symlink, this should preserve it. + cp -a /etc/localtime "${rootfs}/etc/localtime" + fi + + # set minimal hosts + cat < "${rootfs}/etc/hosts" +127.0.0.1 localhost.localdomain localhost ${utsname} +::1 localhost6.localdomain6 localhost6 +EOF + + # setup console and tty[1-4] for login. note that /dev/console and + # /dev/tty[1-4] will be symlinks to the ptys /dev/lxc/console and + # /dev/lxc/tty[1-4] so that package updates can overwrite the symlinks. + # lxc will maintain these links and bind mount ptys over /dev/lxc/* + # since lxc.devttydir is specified in the config. + + # allow root login on console, tty[1-4], and pts/0 for libvirt + cat <> "${rootfs}/etc/securetty" +# LXC (Linux Containers) +lxc/console +lxc/tty1 +lxc/tty2 +lxc/tty3 +lxc/tty4 +# For libvirt/Virtual Machine Monitor +pts/0 +EOF + + if [ "${root_display_password}" = yes ] + then + echo "Setting root password to '$root_password'" + fi + if [ "${root_store_password}" = yes ] + then + touch "${path}/tmp_root_pass" + chmod 600 "${path}/tmp_root_pass" + echo "${root_password}" > "${path}/tmp_root_pass" + echo "Storing root password in '${path}/tmp_root_pass'" + fi + + echo "root:$root_password" | chroot "${rootfs}" chpasswd + + if [ "${root_expire_password}" = yes ] + then + # also set this password as expired to force the user to change it! + chroot "${rootfs}" passwd -e root + fi + + chroot_mounts "${rootfs}" + + # always make sure /etc/resolv.conf is up to date in the target! + cp /etc/resolv.conf "${rootfs}/etc/" + + # rebuild the rpm database based on the target rpm version... + rm -f "${rootfs}"/var/lib/rpm/__db* + chroot "${rootfs}" rpm --rebuilddb + + chroot_umounts "${rootfs}" + + # default systemd target + chroot "${rootfs}" ln -s /lib/systemd/system/multi-user.target \ + /etc/systemd/system/default.target + + # enable networking via systemd-networkd + test -d "${rootfs}/etc/systemd/network" || mkdir "${rootfs}/etc/systemd/network" + cat < "${rootfs}/etc/systemd/network/eth0.network" +[Match] +Name=eth0 + +[Network] +DHCP=both +EOF + mkdir -p "${rootfs}/etc/systemd/system/socket.target.wants" + chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-networkd.socket \ + /etc/systemd/system/socket.target.wants/systemd-networkd.socket + chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-networkd.service \ + /etc/systemd/system/multi-user.target.wants/systemd-networkd.service + mkdir -p "${rootfs}/etc/systemd/system/network-online.target.wants" + chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-networkd-wait-online.service \ + /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service + + # disable traditional network init + chroot "${rootfs}" chkconfig network off + + # enable systemd-resolved + rm -f "${rootfs}/etc/resolv.conf" + chroot "${rootfs}" ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf + chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-resolved.service \ + /etc/systemd/system/multi-user.target.wants/systemd-resolved.service + + # if desired, prevent systemd from over-mounting /tmp with tmpfs + if [ "${masktmp}" -eq 1 ] + then + chroot "${rootfs}" ln -s /dev/null /etc/systemd/system/tmp.mount + fi + + return 0 +} + +copy_configuration() +{ + local rootfs="${1}" + local config="${2}" + local utsname="${3}" + + # include configuration from default.conf if available + grep -q "^lxc." "${lxc_default_conf}" > "${config}" 2>/dev/null + + grep -q "^lxc.rootfs" "${config}" 2>/dev/null || echo " +lxc.rootfs = ${rootfs} +" >> "${config}" + + # The following code is to create static MAC addresses for each + # interface in the container. This code will work for multiple + # interfaces in the default config. It will also strip any + # hwaddr stanzas out of the default config since we can not share + # MAC addresses between containers. + mv "${config}" "${config}.orig" + + local line key + while read -r line + do + # This should catch variable expansions from the default config... + if expr "${line}" : '.*\$' > /dev/null 2>&1 + then + line=$(eval "echo \"${line}\"") + fi + + # There is a tab and a space in the regex bracket below! + # Seems that \s doesn't work in brackets. + key=$(expr "${line}" : '\s*\([^ ]*\)\s*=') + + if [ "${key}" != "lxc.network.hwaddr" ] + then + echo "${line}" >> "${config}" + + if [ "${key}" == "lxc.network.link" ] + then + echo "lxc.network.hwaddr = $(create_hwaddr)" >> "${config}" + fi + fi + done < "${config}.orig" + rm -f "${config}.orig" + + if [ -e "${lxc_template_config}/fedora.common.conf" ] + then + echo " +# Include common configuration +lxc.include = ${lxc_template_config}/fedora.common.conf +" >> "${config}" + fi + + cat <> "${path}/config" +# Container specific configuration +lxc.arch = ${basearch} +lxc.utsname = ${utsname} + +# When using LXC with apparmor, uncomment the next line to run unconfined: +#lxc.aa_profile = unconfined + +# example simple networking setup, uncomment to enable +#lxc.network.type = ${lxc_network_type} +#lxc.network.flags = up +#lxc.network.link = ${lxc_network_link} +#lxc.network.name = eth0 +# Additional example for veth network type +# static MAC address, +#lxc.network.hwaddr = $(create_hwaddr) +# persistent veth device name on host side +# Note: This may potentially collide with other containers of same name! +#lxc.network.veth.pair = v-${name}-e0 +EOF + + if [ $? -ne 0 ] + then + echo "Failed to add configuration" + return 1 + fi + + return 0 +} + +copy_fedora() +{ + local cache="${1}" + local rootfs="${2}" + echo -n "Copying ${cache} to ${rootfs} ... " + + mkdir -p "${rootfs}" && + rsync --archive --hard-links --sparse "${cache}/" "${rootfs}/" && + echo || return 1 + + return 0 +} + +# Generate a random hardware (MAC) address composed of FE followed by +# 5 random bytes... +# +create_hwaddr() +{ + openssl rand -hex 5 | sed -e 's/\(..\)/:\1/g; s/^/fe/' +} + +# Make sure a fully functional rootfs of the requested release and architecture +# will be setup in the given cache directory. If this is a Fedora host the +# commands will run natively otherwise in a minimal Fedora bootstrap chroot. +# +download_fedora() +{ + local cache_rootfs="${1}" + local setup_rootfs="${cache_rootfs%%/rootfs}/partial" + + # suppress errors due to unknown locales + LC_ALL=C + + echo "Downloading ${basearch} rootfs for Fedora ${release} ..." + + # The following variables are going to be overwritten if the rootfs setup + # is run in a separate boostrap environment (can not build natively). + # These are the defaults for the non-boostrap (native) mode. + CHROOT_DIR= + CHROOT_CMD= + INSTALL_ROOT=${setup_rootfs} + + if [ ! "${is_fedora}" ] || [ "${fedora_host_ver}" <= "${FEDORA_VERSION_MINIMAL}" ] + then + # if this is not a supported Fedora host, use minimal bootstrap chroot + echo "Non-Fedora host detected. Checking for bootstrap environment ... " + if ! bootstrap_fedora "${cache}" + then + echo "Error: Fedora Bootstrap setup failed" + return 1 + fi + echo "Using bootstrap environment at ${CHROOT_DIR}" + fi + + if ! mkdir -p "${setup_rootfs}" + then + echo "Error: Failed to create '${setup_rootfs}' directory." + return 1 + fi + + trap revert_rootfs SIGHUP SIGINT SIGTERM + + mkdir -p "${setup_rootfs}/var/lib/rpm" + + # if the setup is going to be run in a chroot, mount the related file systems + if [ -n "${CHROOT_DIR}" ] + then + chroot_mounts "${CHROOT_DIR}" + + # make sure rootfs is available in bootstrap chroot + INSTALL_ROOT="/run/install" + test -d "${CHROOT_DIR}${INSTALL_ROOT}" || mkdir -p "${CHROOT_DIR}${INSTALL_ROOT}" + mount -o bind "${setup_rootfs}" "${CHROOT_DIR}${INSTALL_ROOT}" + fi + + if ! setup_repositories "${cache}" "${basearch}" "${release}" "${mirror}" + then + echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}" + revert_rootfs >/dev/null + return 1 + fi + + # Unforunately "${CHROOT_DIR}${INSTALL_ROOT}/dnf.conf" + if [ -n "${mirror}" ] + then + set_dnf_mirror_url "${CHROOT_DIR}${INSTALL_ROOT}/dnf.conf" + fi + + # install minimal container file system + local pkg_list="dnf initscripts passwd vim-minimal openssh-server openssh-clients dhclient rootfiles policycoreutils fedora-release fedora-repos" + if ! ${CHROOT_CMD}dnf --installroot "${INSTALL_ROOT}" \ + --config="${INSTALL_ROOT}/dnf.conf" \ + --releasever "${release}" \ + ${dnf_args[@]} \ + install ${pkg_list} + then + echo "Error: Failed to setup the rootfs in ${CHROOT_DIR}${INSTALL_ROOT}." + revert_rootfs >/dev/null + return 1 + fi + + unmount_installroot + + # from now on we'll work in the new rootfs + chroot_mounts "${setup_rootfs}" + + # It might happen, that the dnf used above will write an incompatible + # RPM database for the version running in the rootfs. Rebuild it. + echo "Fixing up RPM databases" + rm -f "${CHROOT_DIR}${INSTALL_ROOT}"/var/lib/rpm/__db* + chroot "${setup_rootfs}" rpm --rebuilddb + + chroot_umounts "${setup_rootfs}" + + # reset traps + trap SIGHUP + trap SIGINT + trap SIGTERM + + # use generated rootfs as future cache + mv "${setup_rootfs}" "${cache_rootfs}" + + echo "Download of Fedora rootfs complete." + return 0 +} + +# Install a functional Fedora rootfs into the container root +# +install_fedora() +{ + local rootfs="${1}" + local cache="${2}" + local cache_rootfs="${cache}/${release}-${basearch}/rootfs" + + mkdir -p "${local_state_dir}/lock/subsys/" + ( + if ! flock -x 9 + then + echo "Error: Cache repository is busy." + return 1 + fi + + echo "Checking cache download in ${cache_rootfs} ... " + if [ ! -e "${cache_rootfs}" ] + then + if ! download_fedora "${cache_rootfs}" + then + echo "Error: Failed to download Fedora ${release} (${basearch})" + return 1 + fi + else + echo "Cache found at ${cache_rootfs}. Updating ..." + if ! chroot_update_fedora "${cache_rootfs}" + then + echo "Failed to update cached rootfs, continuing with previously cached version." + else + echo "Fedora update finished." + fi + fi + + trap revert_container SIGHUP SIGINT SIGTERM + + if ! copy_fedora "${cache_rootfs}" "${rootfs}" + then + echo "Error: Failed to copy rootfs" + return 1 + fi + + chroot_mounts "${rootfs}" + + # install additional user provided packages + if [ -n "${packages}" ] + then + # always make sure /etc/resolv.conf is up to date in the target! + cp /etc/resolv.conf "${rootfs}/etc/" + + echo "Installing user requested RPMs: ${packages}" + if ! chroot "${rootfs}" dnf install ${dnf_args[@]} ${packages} + then + echo "Error: Installation of user provided packages failed." + echo "Cleaning up ... " + sleep 3 # wait for all file handles to properly close + chroot_umounts "${setup_rootfs}" + return 1 + fi + fi + + # cleanup dnf cache in new container + chroot "${rootfs}" dnf clean all + + sleep 3 # wait for all file handles to properly close + chroot_umounts "${rootfs}" + + return 0 + ) 9>"${local_state_dir}/lock/subsys/lxc-fedora" + + return $? +} + +# Cleanup partial container +# +revert_container() +{ + echo "Interrupted, so cleaning up ..." + lxc-destroy -n "${name}" 2>/dev/null + # maybe was interrupted before copy config, try to prevent some mistakes + if [ -d "${path}" ] && + [ "${path}" != "/" ] && [ "${path}" != "/tmp" ] && [ "${path}" != "/bin" ] + then + rm -rf "${path}" + fi + echo "Exiting ..." + exit 1 +} + +# Cleanup partial rootfs cache +# +revert_rootfs() +{ + echo "Interrupted, so cleaning up ..." + unmount_installroot + rm -rf "${setup_rootfs}" + echo "Exiting ..." + exit 1 +} + +# Set dnf repository mirror in given repo files +# +set_dnf_mirror_url() +{ + sed -i -e 's/^\(metalink=.*\)$/#\1/g' "${@}" + sed -i -e '/baseurl/ s|^#||g' "${@}" + sed -i -e "/baseurl/ s|http://download.fedoraproject.org/pub/fedora|${mirror}|g" "${@}" +} + +# Setup dnf repository configuration. It can be run in a chroot by specifying +# $CHROOT_DIR (chroot directory) and $CHROOT_CMD (chroot command) and/or +# with an alternative RPM install root defined in $INSTALL_ROOT. +# +setup_repositories() +{ + local cache="${1}" + local target_arch="${2}" + local release="${3}" + local mirror="${4}" + + # download repository packages if not found in cache + pushd "${cache}" >/dev/null + if [ -z "$(ls -1 ./fedora-release-${release}*.noarch.rpm 2>/dev/null)" ] || + [ -z "$(ls -1 ./fedora-repos-${release}*.noarch.rpm 2>/dev/null)" ] + then + # if no mirror given, get an appropriate mirror from the mirror list + if [ -z "${mirror}" ] + then + for trynumber in 1 2 3 4 + do + [ "${trynumber}" != 1 ] && echo -n "Trying again ... " + + # choose some mirrors by parsing directory index + mirror_urls=$(curl --silent --show-error --fail "${MIRRORLIST_URL}?repo=fedora-${release}&arch=${target_arch}" | sed -e '/^http:/!d' -e '2,6!d') + + # shellcheck disable=SC2181 + if [ $? -eq 0 ] && [ -n "${mirror_urls}" ] + then + break + fi + + echo "Warning: Failed to get a mirror on try ${trynumber}." + sleep 3 + done + else + # construct release-specific mirror url + mirror="${mirror}/linux/releases/${release}/Everything/${target_arch}/os" + fi + + # this will fall through if we didn't get any mirrors + for mirror_url in ${mirror:-${mirror_urls}} + do + local release_url="${mirror_url}/Packages/f" + + for pkg in fedora-release-${release} fedora-repos-${release} + do + test -n "$(ls -1 ./${pkg}*.noarch.rpm 2>/dev/null)" && continue + + # query package name by parsing directory index + echo "Requesting '${pkg}' package version from ${release_url}." + pkg_name=$(curl --silent --show-error --fail "${release_url}/" | sed -n -e "/${pkg}/ s/.*href=\"\(${pkg}-.*\.noarch\.rpm\)\">.*/\1/p" | tail -1) + + # shellcheck disable=SC2181 + if [ $? -ne 0 ] || [ -z "${pkg_name}" ] + then + echo "Error: Failed to get '${pkg}' version from ${release_url}/." + continue + fi + + echo "Downloading '${release_url}/${pkg_name} ... " + if ! curl --silent --show-error --fail --remote-name "${release_url}/${pkg_name}" + then + echo "Error: Package download failed." + continue + fi + done + + # if we have both packages continue + if [ -z "$(ls -1 ./fedora-release-${release}*.noarch.rpm 2>/dev/null)" ] || + [ -z "$(ls -1 ./fedora-repos-${release}*.noarch.rpm 2>/dev/null)" ] + then + break + fi + done + fi + + # copy packages to chroot file system + if [ -n "${CHROOT_DIR}" ] + then + cp ./fedora-release-${release}*.noarch.rpm "${CHROOT_DIR}" && + cp ./fedora-repos-${release}*.noarch.rpm "${CHROOT_DIR}" + else + local pkgdir="${cache}" + fi + + # use '--nodeps' to work around 'fedora-release-24-*' bash dependency + ${CHROOT_CMD}rpm --root "${INSTALL_ROOT}" -ivh --nodeps "${pkgdir}"/{fedora-release-${release}*.noarch.rpm,fedora-repos-${release}*.noarch.rpm} + local ret=$? + + # dnf will take $basearch from host, so force the arch we want + sed -i "s|\$basearch|${target_arch}|" ${CHROOT_DIR}${INSTALL_ROOT}/etc/yum.repos.d/* + + popd >/dev/null + + if [ "${ret}" -ne 0 ] + then + echo "Failed to setup repositories in ${CHROOT_DIR}${INSTALL_ROOT}" + exit 1 + fi + + # cleanup installed packages + if [ -n "${CHROOT_DIR}" ] + then + # shellcheck disable=SC2086 + rm -f "${CHROOT_DIR}"/{fedora-release-${release}*.noarch.rpm,fedora-repos-${release}*.noarch.rpm} + fi + + return 0 +} + +# Run dnf update in the given chroot directory. +# +chroot_update_fedora() +{ + local chroot="${1}" + chroot_mounts "${chroot}" + + # always make sure /etc/resolv.conf is up to date in the target! + cp /etc/resolv.conf "${chroot}/etc/" + + chroot "${chroot}" dnf -y update + local ret=$? + + sleep 3 # wait for all file handles to properly close + + chroot_umounts "${chroot}" + + return ${ret} +} + +# Unmount installroot after bootstrapping or on error. +# +unmount_installroot() { + if [ -n "${CHROOT_DIR}" ] + then + sleep 3 # wait for all file handles to properly close + chroot_umounts "${CHROOT_DIR}" 2>/dev/null + umount "${CHROOT_DIR}${INSTALL_ROOT}" 2>/dev/null + fi +} + +usage() +{ + cat </dev/null 2>&1 + then + needed_pkgs="${pkg} ${needed_pkgs}" + fi +done +if [ -n "${needed_pkgs}" ] +then + echo "Error: Missing command(s): ${needed_pkgs}" + exit 1 +fi + +if [ "$(id -u)" != "0" ] +then + echo "This script should be run as 'root'" + exit 1 +fi + +# cleanup cache if requested +cache="${LXC_CACHE_PATH}/fedora" +if [ -n "${clean}" ] +then + clean_cache "${cache}" || exit 1 + exit 0 +fi + +# set container directory +if [ -z "${path}" ] +then + path="${lxc_path}/${name}" +fi + +# set container rootfs and configuration path +config="${path}/config" +if [ -z "${rootfs}" ] +then + # check for 'lxc.rootfs' passed in through default config by lxc-create + if grep -q '^lxc.rootfs' "${config}" 2>/dev/null + then + rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' "${config}") + else + rootfs="${path}/rootfs" + fi +fi + +# set release if not given +if [ -z "${release}" ] +then + if [ "${is_fedora}" ] && [ -n "${fedora_host_ver}" ] + then + echo "Detected Fedora ${fedora_host_ver} host. Set release to ${fedora_host_ver}." + release="${fedora_host_ver}" + else + echo "This is not a Fedora host or release is missing, defaulting release to ${FEDORA_RELEASE_DEFAULT}." + release="${FEDORA_RELEASE_DEFAULT}" + fi +fi +if [ "${release}" -lt "${FEDORA_RELEASE_MIN}" ] +then + echo "Error: Fedora release ${release} is not supported. Set -R at least to ${FEDORA_RELEASE_MIN}." + exit 1 +fi + +# bootstrap rootfs and copy to container file system +if ! install_fedora "${rootfs}" "${cache}" +then + echo "Error: Failed to create Fedora container" + exit 1 +fi + +# customize container file system +if ! configure_fedora "${rootfs}" "${release}" "${utsname}" +then + echo "Error: Failed to configure Fedora container" + exit 1 +fi + +# create container configuration (will be overwritten by newer lxc-create) +if ! copy_configuration "${rootfs}" "${config}" "${utsname}" +then + echo "Error: Failed write container configuration file" + exit 1 +fi + +if [ -n "${clean}" ]; then + clean || exit 1 + exit 0 +fi + +echo "Successfully created container '${name}'" + +if [ "${root_display_password}" = "yes" ] +then + echo "The temporary password for root is: '$root_password' + +You may want to note that password down before starting the container. +" +fi + +if [ "${root_store_password}" = "yes" ] +then + echo "The temporary root password is stored in: + + '${config}/tmp_root_pass' +" +fi + +if [ "${root_prompt_password}" = "yes" ] +then + echo "Invoking the passwd command in the container to set the root password. + + chroot ${rootfs} passwd +" + chroot "${rootfs}" passwd +else + if [ "${root_expire_password}" = "yes" ] + then + if ( mountpoint -q -- "${rootfs}" ) + then + echo "To reset the root password, you can do: + + lxc-start -n ${name} + lxc-attach -n ${name} -- passwd + lxc-stop -n ${name} +" + else + echo " +The root password is set up as expired and will require it to be changed +at first login, which you should do as soon as possible. If you lose the +root password or wish to change it without starting the container, you +can change it from the host by running the following command (which will +also reset the expired flag): + + chroot ${rootfs} passwd +" + fi + fi +fi + +# vim: set ts=4 sw=4 expandtab: