]> git.proxmox.com Git - mirror_lxc.git/commitdiff
Complete rework of lxc-fedora template
authorReto Gantenbein <reto.gantenbein@linuxmonk.ch>
Sat, 31 Dec 2016 00:43:10 +0000 (01:43 +0100)
committerReto Gantenbein <reto.gantenbein@linuxmonk.ch>
Sat, 31 Dec 2016 01:26:15 +0000 (02:26 +0100)
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 <reto.gantenbein@linuxmonk.ch>
templates/Makefile.am
templates/lxc-fedora.in [new file with mode: 0644]

index 6d41a2a54d4c0b1a274499ed160b0f726dfca016..69cd80ebf1c3d13417e0906801377654eccd1055 100644 (file)
@@ -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 (file)
index 0000000..926cb48
--- /dev/null
@@ -0,0 +1,1241 @@
+#!/bin/bash
+
+#
+# template script for generating Fedora container for LXC
+#
+
+#
+# lxc: linux Container library
+
+# Authors:
+# Daniel Lezcano <daniel.lezcano@free.fr>
+# Ramez Hanna <rhanna@informatiq.org>
+# Michael H. Warfield <mhw@WittsEnd.com>
+# Reto Gantenbein <reto.gantenbein@linuxmonk.ch>
+
+# 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 <<EOF > "${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 <<EOF >> "${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 <<EOF > "${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 <<EOF >> "${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 <dnf-2.0 doesn't respect the repository configuration of the
+    # installroot but use the one from the host. This obviously doesn't work
+    # with a custom mirror or target architecture. Therefore a temporary dnf.conf
+    # is created and passed to the chroot command.
+    cat "${CHROOT_DIR}${INSTALL_ROOT}"/etc/yum.repos.d/{fedora,fedora-updates}.repo > "${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 <<EOF
+LXC Container configuration for Fedora images.
+
+Template specific options can be passed to lxc-create after a '--' like this:
+
+  lxc-create --name=NAME -t fedora [OPTION..] -- [TEMPLATE_OPTION..]
+
+Template options:
+
+  -a, --arch             Define what arch the container will be [i686,x86_64]
+  -c, --clean            Clean bootstrap and download cache
+  -d, --debug            Run with 'set -x' to debug errors
+      --fqdn             Fully qualified domain name (FQDN)
+  -h, --help             Print this help text
+      --mask-tmp         Prevent systemd from over-mounting /tmp with tmpfs.
+      --mirror=MIRROR    Fedora mirror to use during installation. Overrides the
+                         FEDORA_RSYNC_URL environment variable (see below).
+  -p, --path=PATH        Path to where the container will be created,
+                         defaults to ${lxc_path}.
+  -P, --packages=PKGS    Comma-separated list of additional RPM packages to
+                         install into the container.
+  -R, --release=RELEASE  Fedora release number of the container, defaults
+                         to host's release if the host is Fedora.
+      --rootfs=ROOTFS    Path for the actual container root file system
+
+Environment variables:
+
+  LXC_CACHE_PATH         Cache directory for image bootstrap. Defaults to
+                         '${LXC_CACHE_PATH}'
+
+  MIRRORLIST_URL         List of Fedora mirrors queried if no custom mirror is
+                         given. Defaults to '${MIRRORLIST_URL}'
+
+  FEDORA_RSYNC_URL       Fedora rsync mirror to use for bootstrap setup.
+                         Defaults to '${FEDORA_RSYNC_URL}'
+
+  FEDORA_RELEASE_DEFAULT Set default Fedora release if not detected from the
+                         host. Is set to '${FEDORA_RELEASE_DEFAULT}'
+
+EOF
+    return 0
+}
+
+options=$(getopt -o a:hp:n:cR:dP: -l help,path:,rootfs:,name:,clean,release:,arch:,debug,fqdn:,mask-tmp,mirror:,packages: -- "$@")
+# shellcheck disable=SC2181
+if [ $? -ne 0 ]; then
+    usage
+    exit 1
+fi
+
+arch=$(uname -m)
+debug=0
+masktmp=0
+
+eval set -- "$options"
+while true
+do
+    case "${1}" in
+        -h|--help)      usage;           exit 0  ;;
+        -n|--name)      name="${2}";     shift 2 ;;
+        -p|--path)      path="${2}";     shift 2 ;;
+        --rootfs)       rootfs="${2}";   shift 2 ;;
+        -a|--arch)      newarch="${2}";  shift 2 ;;
+        -c|--clean)     clean=1;         shift 1 ;;
+        -d|--debug)     debug=1;         shift 1 ;;
+        --fqdn)         utsname="${2}";  shift 2 ;;
+        --mask-tmp)     masktmp=1;       shift 1 ;;
+        --mirror)       mirror="${2}";   shift 2 ;;
+        -P|--packages)  packages="${2}"; shift 2 ;;
+        -R|--release)   release="${2}";  shift 2 ;;
+        --)             shift 1;         break   ;;
+        *)                               break   ;;
+    esac
+done
+
+if [ "${debug}" -eq 1 ]
+then
+    set -x
+fi
+
+# change to a safe directory
+cd || exit $?
+
+# Is this Fedora?
+# Allow for weird remixes like the Raspberry Pi
+#
+# Use the Mitre standard CPE identifier for the release ID if possible...
+# This may be in /etc/os-release or /etc/system-release-cpe.  We
+# should be able to use EITHER.  Give preference to /etc/os-release for now.
+
+if [ -e /etc/os-release ]
+then
+# This is a shell friendly configuration file.  We can just source it.
+# What we're looking for in here is the ID, VERSION_ID and the CPE_NAME
+    . /etc/os-release
+fi
+
+if [ "${CPE_NAME}" = "" ] && [ -e /etc/system-release-cpe ]
+then
+    CPE_NAME=$(head -n1 /etc/system-release-cpe)
+    CPE_URI=$(expr "${CPE_NAME}" : '\([^:]*:[^:]*\)')
+    if [ "${CPE_URI}" != "cpe:/o" ]
+    then
+        CPE_NAME=
+    else
+        echo "Host CPE ID from /etc/system-release-cpe: ${CPE_NAME}"
+        # Probably a better way to do this but sill remain posix
+        # compatible but this works, shrug...
+        # Must be nice and not introduce convenient bashisms here.
+        ID=$(expr ${CPE_NAME} : '[^:]*:[^:]*:[^:]*:\([^:]*\)')
+        VERSION_ID=$(expr ${CPE_NAME} : '[^:]*:[^:]*:[^:]*:[^:]*:\([^:]*\)')
+    fi
+fi
+
+if [ "${ID}" = "fedora" ] && [ -n "${CPE_NAME}" ] && [ -n "${VERSION_ID}" ]
+then
+    fedora_host_ver=${VERSION_ID}
+    is_fedora=true
+fi
+
+basearch=${arch}
+# Map a few architectures to their generic Fedora repository archs.
+# The two ARM archs are a bit of a guesstimate for the v5 and v6
+# archs.  V6 should have hardware floating point (Rasberry Pi).
+# The "arm" arch is safer (no hardware floating point).  So
+# there may be cases where we "get it wrong" for some v6 other
+# than RPi.
+case "$arch" in
+i686) basearch=i386 ;;
+armv3l|armv4l|armv5l) basearch=arm ;;
+armv6l|armv7l|armv8l) basearch=armhfp ;;
+*) ;;
+esac
+
+case "${basearch}" in
+    ppc64|s390x) FEDORA_RSYNC_URL="archives.fedoraproject.org::fedora-secondary" ;;
+    *) ;;
+esac
+
+# Somebody wants to specify an arch.  This is very limited case.
+#       i386/i586/i686 on i386/x86_64
+#           - or -
+#       x86_64 on x86_64
+if [ "${newarch}" != "" ] && [ "${newarch}" != "${arch}" ]
+then
+    case "${newarch}" in
+        i386|i586|i686)
+            if [ "${basearch}" = "i386" ] || [ "${basearch}" = "x86_64" ]
+            then
+                # Make the arch a generic x86 32 bit...
+                basearch=i386
+            else
+                basearch=bad
+            fi
+            ;;
+        *)
+            basearch=bad
+            ;;
+    esac
+
+    if [ "${basearch}" = "bad" ]
+    then
+        echo "Error: You cannot build a ${newarch} Fedora container on a ${arch} host. Sorry!"
+        exit 1
+    fi
+fi
+
+# Let's do something better for the initial root password.
+# It's not perfect but it will defeat common scanning brute force
+# attacks in the case where ssh is exposed.  It will also be set to
+# expired, forcing the user to change it at first login.
+if [ "${root_password}" = "" ]
+then
+    root_password=Root-${name}-${RANDOM}
+else
+    # If it's got a ding in it, try and expand it!
+    if [ "$(expr "${root_password}" : '.*$.')" != 0 ]
+    then
+        root_password=$(eval echo "${root_password}")
+    fi
+
+    # If it has more than 3 consecutive X's in it, feed it
+    # through mktemp as a template.
+    if [ "$(expr "${root_password}" : '.*XXXX')" != 0 ]
+    then
+        root_password=$(mktemp -u "${root_password}")
+    fi
+fi
+
+if [ -z "${utsname}" ]; then
+    utsname=${name}
+fi
+
+# This follows a standard "resolver" convention that an FQDN must have
+# at least two dots or it is considered a local relative host name.
+# If it doesn't, append the dns domain name of the host system.
+#
+# This changes one significant behavior when running
+# "lxc_create -n Container_Name" without using the
+# --fqdn option.
+#
+# Old behavior:
+#    utsname and hostname = Container_Name
+# New behavior:
+#    utsname and hostname = Container_Name.Domain_Name
+
+if [ "$(expr "${utsname}" : '.*\..*\.')" = 0 ]
+then
+    if [ -n "$(dnsdomainname)" ] && [ "$(dnsdomainname)" != "localdomain" ]
+    then
+        utsname="${utsname}.$(dnsdomainname)"
+    fi
+fi
+
+# check if the pre-requisite binaries are available
+prerequisite_pkgs=( curl openssl rsync )
+needed_pkgs=""
+for pkg in "${prerequisite_pkgs[@]}"
+do
+    if ! type "${pkg}" >/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: