--- /dev/null
+#!/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: