From edef9b0e082b816d624d4dbfbcb3d0ee4e0a37a1 Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Sun, 25 Dec 2016 18:09:06 +0100 Subject: [PATCH 01/10] Rename fedora template to fedora-legacy Signed-off-by: Reto Gantenbein --- templates/Makefile.am | 2 +- templates/{lxc-fedora.in => lxc-fedora-legacy.in} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename templates/{lxc-fedora.in => lxc-fedora-legacy.in} (100%) diff --git a/templates/Makefile.am b/templates/Makefile.am index c6c5ea53a..6d41a2a54 100644 --- a/templates/Makefile.am +++ b/templates/Makefile.am @@ -9,7 +9,7 @@ templates_SCRIPTS = \ lxc-cirros \ lxc-debian \ lxc-download \ - lxc-fedora \ + lxc-fedora-legacy \ lxc-gentoo \ lxc-openmandriva \ lxc-opensuse \ diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora-legacy.in similarity index 100% rename from templates/lxc-fedora.in rename to templates/lxc-fedora-legacy.in From 3d7aa7887ac4ff1a1b01bdfcb5954688b7884c1b Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Sat, 31 Dec 2016 01:43:10 +0100 Subject: [PATCH 02/10] 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 --- templates/Makefile.am | 1 + templates/lxc-fedora.in | 1241 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1242 insertions(+) create mode 100644 templates/lxc-fedora.in 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: From 60fd46237e1d2c3f453b36c24ffd947f1927886b Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Sun, 1 Jan 2017 18:53:01 +0100 Subject: [PATCH 03/10] Also add lxc-fedora-legacy to AC_CONFIG_FILES Signed-off-by: Reto Gantenbein --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 46791ddc2..4d5bb06e0 100644 --- a/configure.ac +++ b/configure.ac @@ -882,6 +882,7 @@ AC_CONFIG_FILES([ templates/lxc-debian templates/lxc-download templates/lxc-fedora + templates/lxc-fedora-legacy templates/lxc-gentoo templates/lxc-openmandriva templates/lxc-opensuse From df3e3fa11a6cd7e9a3e1fca781941f2893a9624a Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Mon, 2 Jan 2017 17:13:11 +0100 Subject: [PATCH 04/10] Explicitly mount squashfs in case autodetection fails Signed-off-by: Reto Gantenbein --- templates/lxc-fedora.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index 926cb4867..6beb2a0f3 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -163,7 +163,7 @@ bootstrap_fedora() fi echo "Mounting LiveOS squashfs file system." - if ! mount -o loop "${cache}/install.img" squashfs/ + if ! mount -o loop -t squashfs "${cache}/install.img" squashfs/ then echo " Error: Mount of LiveOS squashfs image failed From 7b40d7287eff585ec8673293bd8db4b70f521412 Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Fri, 6 Jan 2017 07:03:04 +0100 Subject: [PATCH 05/10] Fix version comparison Signed-off-by: Reto Gantenbein --- templates/lxc-fedora.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index 6beb2a0f3..c94940c12 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -540,7 +540,7 @@ download_fedora() CHROOT_CMD= INSTALL_ROOT=${setup_rootfs} - if [ ! "${is_fedora}" ] || [ "${fedora_host_ver}" <= "${FEDORA_VERSION_MINIMAL}" ] + if [ ! "${is_fedora}" ] || [ "${fedora_host_ver}" -lt "${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 ... " From c172e264de76648f60d5d8f85b2f3e7027f752b5 Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Fri, 6 Jan 2017 07:06:03 +0100 Subject: [PATCH 06/10] Fix installing multiple packages given with '--packages' Signed-off-by: Reto Gantenbein --- templates/lxc-fedora.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index c94940c12..05a3b6506 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -679,7 +679,7 @@ install_fedora() cp /etc/resolv.conf "${rootfs}/etc/" echo "Installing user requested RPMs: ${packages}" - if ! chroot "${rootfs}" dnf install ${dnf_args[@]} ${packages} + if ! chroot "${rootfs}" dnf install ${dnf_args[@]} $(tr ',' ' ' <<< "${packages}") then echo "Error: Installation of user provided packages failed." echo "Cleaning up ... " From f919f5ca113a9ad02624c06baa2416e0e4646732 Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Sat, 7 Jan 2017 02:36:38 +0100 Subject: [PATCH 07/10] Fix chroot rpmdb path Signed-off-by: Reto Gantenbein --- templates/lxc-fedora.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index 05a3b6506..ae9f8f03d 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -611,7 +611,7 @@ download_fedora() # 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* + rm -f "${setup_rootfs}"/var/lib/rpm/__db* chroot "${setup_rootfs}" rpm --rebuilddb chroot_umounts "${setup_rootfs}" From 91a5f346123eec598f43932b71b80cc48a60e6da Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Thu, 5 Jan 2017 07:22:35 +0100 Subject: [PATCH 08/10] Restrict package locales to en_US Adjust locale setup to official Fedora cloud setup. This will shrink the image size for e.g. Fedora 25 from around 350MB to 260MB. Signed-off-by: Reto Gantenbein --- templates/lxc-fedora.in | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index ae9f8f03d..19d5808fe 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -530,6 +530,7 @@ download_fedora() # suppress errors due to unknown locales LC_ALL=C + LANG=en_US echo "Downloading ${basearch} rootfs for Fedora ${release} ..." @@ -614,6 +615,21 @@ download_fedora() rm -f "${setup_rootfs}"/var/lib/rpm/__db* chroot "${setup_rootfs}" rpm --rebuilddb + # Restrict locale for installed packages to en_US to shrink image size + # following: https://pagure.io/fedora-kickstarts/blob/master/f/fedora-cloud-base.ks + echo "Cleanup locales and language files ..." + find "${setup_rootfs}/usr/share/locale" -mindepth 1 -maxdepth 1 -type d \ + -not -name "${LANG}" -exec rm -rf {} + + + chroot "${setup_rootfs}" localedef --list-archive | grep -v ^"${LANG}" | xargs \ + chroot "${setup_rootfs}" localedef --delete-from-archive + + mv -f "${setup_rootfs}/usr/lib/locale/locale-archive" \ + "${setup_rootfs}/usr/lib/locale/locale-archive.tmpl" + chroot "${setup_rootfs}" build-locale-archive + + echo "%_install_langs C:en:${LANG}:${LANG}.UTF-8" > "${setup_rootfs}/etc/rpm/macros.image-language-conf" + chroot_umounts "${setup_rootfs}" # reset traps From 52c4c3682dc0194762949fe2b7318c908c2abb89 Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Mon, 6 Feb 2017 08:17:13 +0100 Subject: [PATCH 09/10] Query Fedora mirror list over HTTPS Signed-off-by: Reto Gantenbein --- templates/lxc-fedora.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index 19d5808fe..cec8d36eb 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -31,7 +31,7 @@ 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} +MIRRORLIST_URL=${MIRRORLIST_URL:-https://mirrors.fedoraproject.org/mirrorlist} local_state_dir="@LOCALSTATEDIR@" lxc_path="@LXCPATH@" From 577eb5e3e347df7a71b60c713443cad82b18e993 Mon Sep 17 00:00:00 2001 From: Reto Gantenbein Date: Tue, 7 Feb 2017 06:59:15 +0100 Subject: [PATCH 10/10] Change Fedora mirror downloads to https by default, rsync optional This mainly affects the download of the bootstrap image when running on a non-Fedora host and the initial download of the repo and release RPMs. The container rootfs creation will then be verified by dnf against the GPG signatures in the repos RPM. Signed-off-by: Reto Gantenbein --- templates/lxc-fedora.in | 79 +++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index cec8d36eb..9e9bb8d38 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -141,17 +141,28 @@ bootstrap_fedora() local image_path="/linux/releases/${FEDORA_RELEASE_DEFAULT}/Everything/${arch}/os/images/install.img" local ret=1 - if [ -n "${mirror}" ] + if [ -n "${rsync}" ] 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=$? + else + if [ -z "${mirror}" ] + then + get_mirrors || return $? + fi + for url in ${mirror:${mirror_urls}} + do + echo "Downloading LiveOS squashfs image from ${url} ... " + if ! curl --silent --show-error --fail --remote-name "${mirror}${image_path}" + then + echo "Error: Image download failed." + continue + fi + ret=$? + done fi + if [ "${ret}" != 0 ] || [ ! -s install.img ] then echo "Error: Download of squashfs image failed." @@ -644,6 +655,36 @@ download_fedora() return 0 } +# Query the Fedora mirrorlist for several HTTPS mirrors +# +get_mirrors() +{ + 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 '/^https:/!d' | sed '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 + + if [ -z "${mirror_urls}" ] + then + echo "Error: Failed to retrieve Fedora mirror URL. Please use '-m MIRROR' option." + return 1 + fi + + return 0 +} + # Install a functional Fedora rootfs into the container root # install_fedora() @@ -772,28 +813,12 @@ setup_repositories() # 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 + get_mirrors || return $? 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" @@ -912,8 +937,7 @@ Template options: --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). + --mirror=MIRROR Fedora mirror to use during installation. -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 @@ -921,6 +945,8 @@ Template options: -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 + --rsync Use rsync instead of HTTPS to download bootstrap + image (insecure). Environment variables: @@ -930,7 +956,7 @@ Environment variables: 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. + FEDORA_RSYNC_URL Fedora rsync URL to use for bootstrap with '--rsync'. Defaults to '${FEDORA_RSYNC_URL}' FEDORA_RELEASE_DEFAULT Set default Fedora release if not detected from the @@ -967,6 +993,7 @@ do --mirror) mirror="${2}"; shift 2 ;; -P|--packages) packages="${2}"; shift 2 ;; -R|--release) release="${2}"; shift 2 ;; + --rsync) rsync=1; shift 1 ;; --) shift 1; break ;; *) break ;; esac