4 # template script for generating Fedora container for LXC
8 # lxc: linux Container library
11 # Daniel Lezcano <daniel.lezcano@free.fr>
12 # Ramez Hanna <rhanna@informatiq.org>
13 # Michael H. Warfield <mhw@WittsEnd.com>
14 # Reto Gantenbein <reto.gantenbein@linuxmonk.ch>
16 # This library is free software; you can redistribute it and/or
17 # modify it under the terms of the GNU Lesser General Public
18 # License as published by the Free Software Foundation; either
19 # version 2.1 of the License, or (at your option) any later version.
21 # This library is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 # Lesser General Public License for more details.
26 # You should have received a copy of the GNU Lesser General Public
27 # License along with this library; if not, write to the Free Software
28 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32 FEDORA_RELEASE_DEFAULT
=${FEDORA_RELEASE_DEFAULT:-25}
33 FEDORA_RSYNC_URL
="${FEDORA_RSYNC_URL:-archives.fedoraproject.org::fedora-enchilada}"
34 MIRRORLIST_URL
=${MIRRORLIST_URL:-https://mirrors.fedoraproject.org/mirrorlist}
36 local_state_dir
="@LOCALSTATEDIR@"
38 lxc_template_config
="@LXCTEMPLATECONFIG@"
39 lxc_default_conf
="@LXC_DEFAULT_CONFIG@"
41 # allows the cache directory to be set by environment variable
42 LXC_CACHE_PATH
="${LXC_CACHE_PATH:-"${local_state_dir}/cache/lxc"}"
44 # these are only going into comments in the resulting config...
46 lxc_network_link
=lxcbr0
48 # Some combinations of the tuning knobs below do not exactly make sense.
51 # If the "root_password" is non-blank, use it, else set a default.
52 # This can be passed to the script as an environment variable and is
53 # set by a shell conditional assignment. Looks weird but it is what it is.
55 # If the root password contains a ding ($) then try to expand it.
56 # That will pick up things like ${name} and ${RANDOM}.
57 # If the root password contains more than 3 consecutive X's, pass it as
58 # a template to mktemp and take the result.
60 # If root_display_password = yes, display the temporary root password at exit.
61 # If root_store_password = yes, store it in the configuration directory
62 # If root_prompt_password = yes, invoke "passwd" to force the user to change
63 # the root password after the container is created.
64 # If root_expire_password = yes, you will be prompted to change the root
65 # password at the first login.
67 # These are conditional assignments... The can be overridden from the
68 # preexisting environment variables...
70 # Make sure this is in single quotes to defer expansion to later!
71 # :{root_password='Root-${name}-${RANDOM}'}
72 : "${root_password='Root-${name}-XXXXXX'}"
74 # Now, it doesn't make much sense to display, store, and force change
75 # together. But, we gotta test, right???
76 : "${root_display_password='no'}"
77 : "${root_store_password='yes'}"
78 # Prompting for something interactive has potential for mayhem
79 # with users running under the API... Don't default to "yes"
80 : "${root_prompt_password='no'}"
82 # Expire root password? Default to yes, but can be overridden from
83 # the environment variable
84 : "${root_expire_password='yes'}"
86 # detect use under userns (unsupported)
88 [ "${arg}" = "--" ] && break
89 if [ "${arg}" = "--mapped-uid" ] ||
[ "${arg}" = "--mapped-gid" ]
91 echo "This template can't be used for unprivileged containers." 1>&2
92 echo "You may want to try the \"download\" template instead." 1>&2
97 # make sure the usual locations are in PATH
98 export PATH
=${PATH}:/usr
/sbin
:/usr
/bin
:/sbin
:/bin
100 # dnf package manager arguments
101 dnf_args
=( --assumeyes --best --allowerasing --disablerepo=* --enablerepo=fedora
--enablerepo=updates
)
103 # This function is going to setup a minimal host-arch native Fedora bootstrap
104 # environment which will be used to create new containers on non-Fedora hosts.
110 local bootstrap
="${cache}/bootstrap"
111 if [ -d "${bootstrap}" ]
113 echo "Existing Fedora bootstrap environment found. Testing ..."
114 if chroot_update_fedora
"${bootstrap}"
116 CHROOT_DIR
="${bootstrap}"
117 CHROOT_CMD
="chroot ${CHROOT_DIR} "
119 echo "Bootstrap environment appears to be functional."
122 echo "Error: Bootstrap environment detected in ${bootstrap}"
123 echo "but appears to be non-functional. Please remove and restart."
128 echo "Setting up new Fedora ${FEDORA_RELEASE_DEFAULT} (${basearch}) bootstrap environment."
130 [[ -d "${cache}" ]] || mkdir
-p "${cache}"
132 tmp_bootstrap_dir
=$
( mktemp
-d --tmpdir="${cache}" bootstrap_XXXXXX
)
133 pushd "${tmp_bootstrap_dir}" >/dev
/null
135 mkdir squashfs liveos bootstrap
137 # download the LiveOS squashfs image
138 if [ ! -f "${cache}/install.img" ]
140 local os_path
="linux/releases/${FEDORA_RELEASE_DEFAULT}/Everything/${basearch}/os"
141 local image_path
="images/install.img"
146 echo "Syncing LiveOS squashfs image from ${FEDORA_RSYNC_URL} ... "
147 rsync
--archive --info=progress
"${FEDORA_RSYNC_URL}/${os_path}/${image_path}" .
150 if [ -z "${mirror}" ]
152 get_mirrors
"${FEDORA_RELEASE_DEFAULT}" "${basearch}" ||
return $?
154 local mirror_url
="${mirror}/${os_path}"
156 for url
in ${mirror:-${mirror_urls}}
158 echo "Downloading LiveOS squashfs image from ${url} ... "
159 curl
--silent --show-error --fail --remote-name "${url}/${image_path}"
170 if [ "${ret}" != 0 ] ||
[ ! -s install.img
]
172 echo "Error: Download of squashfs image failed."
175 mv install.img
"${cache}/"
177 echo "Using cached LiveOS squashfs image."
180 echo "Mounting LiveOS squashfs file system."
181 if ! mount
-o loop
-t squashfs
"${cache}/install.img" squashfs
/
184 Error: Mount of LiveOS squashfs image failed
185 --------------------------------------------
186 You must have squashfs support available to mount image. LiveOS image is now
187 cached. Process may be rerun without penalty of downloading LiveOS again. If
188 LiveOS is corrupt, remove ${cache}/install.img
193 # mount contained LiveOS
194 if ! mount
-o loop squashfs
/LiveOS
/rootfs.img liveos
197 Error: Mount of LiveOS stage0 rootfs image failed
198 -------------------------------------------------
199 LiveOS download may be corrupt. Remove ${cache}/LiveOS
200 to force a new download.
205 echo "Copying LiveOS content to bootstrap environment ... "
206 if ! rsync
--archive --acls --hard-links --sparse liveos
/. bootstrap
/
208 echo "Error: Build of bootstrap environment failed."
209 echo "Keeping directory ${tmp_bootstrap_dir} for your investigation."
213 # unmount liveos mounts - we're done with liveos at this point
217 # customize bootstrap rootfs
218 pushd bootstrap
>/dev
/null
220 # setup repositories in bootstrap chroot
222 CHROOT_CMD
="chroot ${CHROOT_DIR} "
224 if ! setup_repositories
"${cache}" "${basearch}" "${FEDORA_RELEASE_DEFAULT}" "${mirror}"
226 echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
229 if [ -n "${mirror}" ]
231 set_dnf_mirror_url .
/etc
/yum.repos.d
/fedora
*.repo
234 # make sure /etc/resolv.conf is up to date in the chroot
235 cp /etc
/resolv.conf .
/etc
/
237 # verify bootstrap chroot by running a dnf update
238 chroot_update_fedora
"$(pwd)"
245 echo "Error: Build of bootstrap environment failed."
246 echo "Keeping directory ${tmp_bootstrap_dir} for your investigation."
250 mv bootstrap
"${cache}"
253 rm -rf "${tmp_bootstrap_dir}"
255 CHROOT_DIR
="${bootstrap}"
256 CHROOT_CMD
="chroot ${CHROOT_DIR} "
258 echo "Fedora bootstrap environment successfully created."
264 test -n "${1}" && local chroot
="${1}" ||
return 1
266 mount
-t proc proc
"${chroot}/proc" &&
267 mount
-o bind /dev
"${chroot}/dev"
272 test -n "${1}" && local chroot
="${1}" ||
return 1
274 umount
"${chroot}/proc" &&
275 umount
"${chroot}/dev"
282 test ! -e "${cache}" && return 0
284 # lock, so we won't purge while someone is creating a repository
288 echo "Error: Cache repository is busy."
292 echo -n "Purging the Fedora bootstrap and download cache ... "
293 rm --preserve-root --one-file-system -rf "${cache}" && echo "Done." ||
exit 1
297 ) 9>"${local_state_dir}/lock/subsys/lxc-fedora"
302 # Customize container rootfs
311 mkdir
-p "${rootfs}/selinux"
312 echo 0 > "${rootfs}/selinux/enforce"
314 # also kill it in the /etc/selinux/config file if it's there...
315 if [ -f "${rootfs}/etc/selinux/config" ]
317 sed -i '/^SELINUX=/s/.*/SELINUX=disabled/' "${rootfs}/etc/selinux/config"
320 # nice catch from Dwight Engen in the Oracle template.
321 # wantonly plagerized here with much appreciation.
322 if [ -f "${rootfs}/usr/sbin/selinuxenabled" ]
324 rm -f "${rootfs}/usr/sbin/selinuxenabled"
325 ln -s /bin
/false
"${rootfs}/usr/sbin/selinuxenabled"
329 echo "${utsname}" > "${rootfs}/etc/hostname"
331 # set default localtime to the host localtime if not set...
332 if [ -e /etc
/localtime
] && [ ! -e "${rootfs}/etc/localtime" ]
334 # if /etc/localtime is a symlink, this should preserve it.
335 cp -a /etc
/localtime
"${rootfs}/etc/localtime"
339 cat <<EOF > "${rootfs}/etc/hosts"
340 127.0.0.1 localhost.localdomain localhost ${utsname}
341 ::1 localhost6.localdomain6 localhost6
344 # setup console and tty[1-4] for login. note that /dev/console and
345 # /dev/tty[1-4] will be symlinks to the ptys /dev/lxc/console and
346 # /dev/lxc/tty[1-4] so that package updates can overwrite the symlinks.
347 # lxc will maintain these links and bind mount ptys over /dev/lxc/*
348 # since lxc.tty.dir is specified in the config.
350 # allow root login on console, tty[1-4], and pts/0 for libvirt
351 cat <<EOF >> "${rootfs}/etc/securetty"
352 # LXC (Linux Containers)
358 # For libvirt/Virtual Machine Monitor
362 if [ "${root_display_password}" = yes ]
364 echo "Setting root password to '$root_password'"
366 if [ "${root_store_password}" = yes ]
368 touch "${path}/tmp_root_pass"
369 chmod 600 "${path}/tmp_root_pass"
370 echo "${root_password}" > "${path}/tmp_root_pass"
371 echo "Storing root password in '${path}/tmp_root_pass'"
374 echo "root:$root_password" | chroot
"${rootfs}" chpasswd
376 if [ "${root_expire_password}" = yes ]
378 # also set this password as expired to force the user to change it!
379 chroot
"${rootfs}" passwd
-e root
382 chroot_mounts
"${rootfs}"
384 # always make sure /etc/resolv.conf is up to date in the target!
385 cp /etc
/resolv.conf
"${rootfs}/etc/"
387 # rebuild the rpm database based on the target rpm version...
388 rm -f "${rootfs}"/var
/lib
/rpm
/__db
*
389 chroot
"${rootfs}" rpm
--rebuilddb
391 chroot_umounts
"${rootfs}"
393 # default systemd target
394 chroot
"${rootfs}" ln -s /lib
/systemd
/system
/multi-user.target \
395 /etc
/systemd
/system
/default.target
397 # enable networking via systemd-networkd
398 test -d "${rootfs}/etc/systemd/network" || mkdir
"${rootfs}/etc/systemd/network"
399 cat <<EOF > "${rootfs}/etc/systemd/network/eth0.network"
406 mkdir
-p "${rootfs}/etc/systemd/system/socket.target.wants"
407 chroot
"${rootfs}" ln -s /usr
/lib
/systemd
/system
/systemd-networkd.socket \
408 /etc
/systemd
/system
/socket.target.wants
/systemd-networkd.socket
409 chroot
"${rootfs}" ln -s /usr
/lib
/systemd
/system
/systemd-networkd.service \
410 /etc
/systemd
/system
/multi-user.target.wants
/systemd-networkd.service
411 mkdir
-p "${rootfs}/etc/systemd/system/network-online.target.wants"
412 chroot
"${rootfs}" ln -s /usr
/lib
/systemd
/system
/systemd-networkd-wait-online.service \
413 /etc
/systemd
/system
/network-online.target.wants
/systemd-networkd-wait-online.service
415 # disable traditional network init
416 chroot
"${rootfs}" chkconfig network off
418 # enable systemd-resolved
419 rm -f "${rootfs}/etc/resolv.conf"
420 chroot
"${rootfs}" ln -s /run
/systemd
/resolve
/resolv.conf
/etc
/resolv.conf
421 chroot
"${rootfs}" ln -s /usr
/lib
/systemd
/system
/systemd-resolved.service \
422 /etc
/systemd
/system
/multi-user.target.wants
/systemd-resolved.service
424 # if desired, prevent systemd from over-mounting /tmp with tmpfs
425 if [ "${masktmp}" -eq 1 ]
427 chroot
"${rootfs}" ln -s /dev
/null
/etc
/systemd
/system
/tmp.mount
439 # include configuration from default.conf if available
440 grep -q "^lxc." "${lxc_default_conf}" > "${config}" 2>/dev
/null
442 grep -q "^lxc.rootfs.path" "${config}" 2>/dev
/null ||
echo "
443 lxc.rootfs.path = ${rootfs}
446 # The following code is to create static MAC addresses for each
447 # interface in the container. This code will work for multiple
448 # interfaces in the default config. It will also strip any
449 # hwaddr stanzas out of the default config since we can not share
450 # MAC addresses between containers.
451 mv "${config}" "${config}.orig"
456 # This should catch variable expansions from the default config...
457 if expr "${line}" : '.*\$' > /dev
/null
2>&1
459 line
=$
(eval "echo \"${line}\"")
462 # There is a tab and a space in the regex bracket below!
463 # Seems that \s doesn't work in brackets.
464 key
=$
(expr "${line}" : '\s*\([^ ]*\)\s*=')
466 if [ "${key}" != "lxc.net.0.hwaddr" ]
468 echo "${line}" >> "${config}"
470 if [ "${key}" == "lxc.net.0.link" ]
472 echo "lxc.net.0.hwaddr = $(create_hwaddr)" >> "${config}"
475 done < "${config}.orig"
476 rm -f "${config}.orig"
478 if [ -e "${lxc_template_config}/fedora.common.conf" ]
481 # Include common configuration
482 lxc.include = ${lxc_template_config}/fedora.common.conf
486 cat <<EOF >> "${path}/config"
487 # Container specific configuration
488 lxc.arch = ${basearch}
489 lxc.uts.name = ${utsname}
491 # When using LXC with apparmor, uncomment the next line to run unconfined:
492 #lxc.aa_profile = unconfined
494 # example simple networking setup, uncomment to enable
495 #lxc.net.0.type = ${lxc_network_type}
496 #lxc.net.0.flags = up
497 #lxc.net.0.link = ${lxc_network_link}
498 #lxc.net.0.name = eth0
499 # Additional example for veth network type
500 # static MAC address,
501 #lxc.net.0.hwaddr = $(create_hwaddr)
502 # persistent veth device name on host side
503 # Note: This may potentially collide with other containers of same name!
504 #lxc.net.0.veth.pair = v-${name}-e0
509 echo "Failed to add configuration"
520 echo -n "Copying ${cache} to ${rootfs} ... "
522 mkdir
-p "${rootfs}" &&
523 rsync
--archive --hard-links --sparse "${cache}/" "${rootfs}/" &&
529 # Generate a random hardware (MAC) address composed of FE followed by
534 openssl rand
-hex 5 |
sed -e 's/\(..\)/:\1/g; s/^/fe/'
537 # Make sure a fully functional rootfs of the requested release and architecture
538 # will be setup in the given cache directory. If this is a Fedora host the
539 # commands will run natively otherwise in a minimal Fedora bootstrap chroot.
543 local cache_rootfs
="${1}"
544 local setup_rootfs
="${cache_rootfs%%/rootfs}/partial"
546 # suppress errors due to unknown locales
550 echo "Downloading ${basearch} rootfs for Fedora ${release} ..."
552 # The following variables are going to be overwritten if the rootfs setup
553 # is run in a separate boostrap environment (can not build natively).
554 # These are the defaults for the non-boostrap (native) mode.
557 INSTALL_ROOT
=${setup_rootfs}
559 if [ ! "${is_fedora}" ] || [ "${fedora_host_ver}" -lt "${FEDORA_VERSION_MINIMAL}" ]
561 # if this is not a supported Fedora host, use minimal bootstrap chroot
562 echo "Non-Fedora host detected. Checking for bootstrap environment ... "
563 if ! bootstrap_fedora
"${cache}"
565 echo "Error: Fedora Bootstrap setup failed"
568 echo "Using bootstrap environment at ${CHROOT_DIR}"
571 if ! mkdir
-p "${setup_rootfs}"
573 echo "Error: Failed to create '${setup_rootfs}' directory."
577 trap revert_rootfs SIGHUP SIGINT SIGTERM
579 mkdir
-p "${setup_rootfs}/var/lib/rpm"
581 # if the setup is going to be run in a chroot, mount the related file systems
582 if [ -n "${CHROOT_DIR}" ]
584 chroot_mounts
"${CHROOT_DIR}"
586 # make sure rootfs is available in bootstrap chroot
587 INSTALL_ROOT
="/run/install"
588 test -d "${CHROOT_DIR}${INSTALL_ROOT}" || mkdir -p "${CHROOT_DIR}${INSTALL_ROOT}"
589 mount
-o bind "${setup_rootfs}" "${CHROOT_DIR}${INSTALL_ROOT}"
592 if ! setup_repositories
"${cache}" "${basearch}" "${release}" "${mirror}"
594 echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
595 revert_rootfs
>/dev
/null
599 # Unforunately <dnf-2.0 doesn't respect the repository configuration of the
600 # installroot but use the one from the host. This obviously doesn't work
601 # with a custom mirror or target architecture. Therefore a temporary dnf.conf
602 # is created and passed to the chroot command.
603 cat "${CHROOT_DIR}${INSTALL_ROOT}"/etc/yum.repos.d/{fedora,fedora-updates}.repo > "${CHROOT_DIR}${INSTALL_ROOT}/dnf.conf"
604 if [ -n "${mirror}" ]
606 set_dnf_mirror_url
"${CHROOT_DIR}${INSTALL_ROOT}/dnf.conf"
609 # install minimal container file system
610 local pkg_list
="dnf initscripts passwd vim-minimal openssh-server openssh-clients dhclient rootfiles policycoreutils fedora-release fedora-repos"
611 if ! ${CHROOT_CMD}dnf
--installroot "${INSTALL_ROOT}" \
612 --config="${INSTALL_ROOT}/dnf.conf" \
613 --releasever "${release}" \
617 echo "Error: Failed to setup the rootfs in ${CHROOT_DIR}${INSTALL_ROOT}."
618 revert_rootfs
>/dev
/null
624 # from now on we'll work in the new rootfs
625 chroot_mounts
"${setup_rootfs}"
627 # It might happen, that the dnf used above will write an incompatible
628 # RPM database for the version running in the rootfs. Rebuild it.
629 echo "Fixing up RPM databases"
630 rm -f "${setup_rootfs}"/var
/lib
/rpm
/__db
*
631 chroot
"${setup_rootfs}" rpm
--rebuilddb
633 # Restrict locale for installed packages to en_US to shrink image size
634 # following: https://pagure.io/fedora-kickstarts/blob/master/f/fedora-cloud-base.ks
635 echo "Cleanup locales and language files ..."
636 find "${setup_rootfs}/usr/share/locale" -mindepth 1 -maxdepth 1 -type d \
637 -not -name "${LANG}" -exec rm -rf {} +
639 chroot
"${setup_rootfs}" localedef
--list-archive |
grep -v ^
"${LANG}" |
xargs \
640 chroot
"${setup_rootfs}" localedef
--delete-from-archive
642 mv -f "${setup_rootfs}/usr/lib/locale/locale-archive" \
643 "${setup_rootfs}/usr/lib/locale/locale-archive.tmpl"
644 chroot
"${setup_rootfs}" build-locale-archive
646 echo "%_install_langs C:en:${LANG}:${LANG}.UTF-8" > "${setup_rootfs}/etc/rpm/macros.image-language-conf"
648 chroot_umounts
"${setup_rootfs}"
655 # use generated rootfs as future cache
656 mv "${setup_rootfs}" "${cache_rootfs}"
658 echo "Download of Fedora rootfs complete."
662 # Query the Fedora mirrorlist for several HTTPS mirrors
667 local mirror_arch
="${2}"
669 for trynumber
in 1 2 3 4
671 [ "${trynumber}" != 1 ] && echo -n "Trying again ... "
673 # choose some mirrors by parsing directory index
674 mirror_urls
=$
(curl
--silent --show-error --fail "${MIRRORLIST_URL}?repo=fedora-${release}&arch=${mirror_arch}" |
sed '/^https:/!d' |
sed '2,6!d')
676 # shellcheck disable=SC2181
677 if [ $?
-eq 0 ] && [ -n "${mirror_urls}" ]
682 echo "Warning: Failed to get a mirror on try ${trynumber}."
686 if [ -z "${mirror_urls}" ]
688 echo "Error: Failed to retrieve Fedora mirror URL. Please use '-m MIRROR' option."
695 # Install a functional Fedora rootfs into the container root
701 local cache_rootfs
="${cache}/${release}-${basearch}/rootfs"
703 mkdir
-p "${local_state_dir}/lock/subsys/"
707 echo "Error: Cache repository is busy."
711 echo "Checking cache download in ${cache_rootfs} ... "
712 if [ ! -e "${cache_rootfs}" ]
714 if ! download_fedora
"${cache_rootfs}"
716 echo "Error: Failed to download Fedora ${release} (${basearch})"
720 echo "Cache found at ${cache_rootfs}. Updating ..."
721 if ! chroot_update_fedora
"${cache_rootfs}"
723 echo "Failed to update cached rootfs, continuing with previously cached version."
725 echo "Fedora update finished."
729 trap revert_container SIGHUP SIGINT SIGTERM
731 if ! copy_fedora
"${cache_rootfs}" "${rootfs}"
733 echo "Error: Failed to copy rootfs"
737 chroot_mounts
"${rootfs}"
739 # install additional user provided packages
740 if [ -n "${packages}" ]
742 # always make sure /etc/resolv.conf is up to date in the target!
743 cp /etc
/resolv.conf
"${rootfs}/etc/"
745 echo "Installing user requested RPMs: ${packages}"
746 if ! chroot
"${rootfs}" dnf install ${dnf_args[@]} $(tr ',' ' ' <<< "${packages}")
748 echo "Error: Installation of user provided packages failed."
749 echo "Cleaning up ... "
750 sleep 3 # wait for all file handles to properly close
751 chroot_umounts
"${setup_rootfs}"
756 # cleanup dnf cache in new container
757 chroot
"${rootfs}" dnf clean all
759 sleep 3 # wait for all file handles to properly close
760 chroot_umounts
"${rootfs}"
763 ) 9>"${local_state_dir}/lock/subsys/lxc-fedora"
768 # Cleanup partial container
772 echo "Interrupted, so cleaning up ..."
773 lxc-destroy
-n "${name}" 2>/dev
/null
774 # maybe was interrupted before copy config, try to prevent some mistakes
775 if [ -d "${path}" ] &&
776 [ "${path}" != "/" ] && [ "${path}" != "/tmp" ] && [ "${path}" != "/bin" ]
784 # Cleanup partial rootfs cache
788 echo "Interrupted, so cleaning up ..."
790 rm -rf "${setup_rootfs}"
795 # Set dnf repository mirror in given repo files
799 sed -i -e 's/^\(metalink=.*\)$/#\1/g' "${@}"
800 sed -i -e '/baseurl/ s|^#||g' "${@}"
801 sed -i -e "/baseurl/ s|http://download.fedoraproject.org/pub/fedora|${mirror}|g" "${@}"
804 # Setup dnf repository configuration. It can be run in a chroot by specifying
805 # $CHROOT_DIR (chroot directory) and $CHROOT_CMD (chroot command) and/or
806 # with an alternative RPM install root defined in $INSTALL_ROOT.
811 local target_arch
="${2}"
815 # download repository packages if not found in cache
816 pushd "${cache}" >/dev
/null
817 if [ -z "$(ls -1 ./fedora-release-${release}*.noarch.rpm 2>/dev/null)" ] ||
818 [ -z "$(ls -1 ./fedora-repos-${release}*.noarch.rpm 2>/dev/null)" ]
820 # if no mirror given, get an appropriate mirror from the mirror list
821 if [ -z "${mirror}" ]
823 get_mirrors
"${release}" "${target_arch}" ||
return $?
825 # construct release-specific mirror url
826 mirror
="${mirror}/linux/releases/${release}/Everything/${target_arch}/os"
829 for mirror_url
in ${mirror:-${mirror_urls}}
831 local release_url
="${mirror_url}/Packages/f"
833 for pkg
in fedora-release-
${release} fedora-repos-
${release}
835 test -n "$(ls -1 ./${pkg}*.noarch.rpm 2>/dev/null)" && continue
837 # query package name by parsing directory index
838 echo "Requesting '${pkg}' package version from ${release_url}."
839 pkg_name
=$
(curl
--silent --show-error --fail "${release_url}/" | sed -n -e "/${pkg}/ s/.*href=\"\(${pkg}-.*\.noarch\.rpm\)\">.*/\1/p" |
tail -1)
841 # shellcheck disable=SC2181
842 if [ $?
-ne 0 ] ||
[ -z "${pkg_name}" ]
844 echo "Error: Failed to get '${pkg}' version from ${release_url}/."
848 echo "Downloading '${release_url}/${pkg_name} ... "
849 if ! curl
--silent --show-error --fail --remote-name "${release_url}/${pkg_name}"
851 echo "Error: Package download failed."
856 # if we have both packages continue
857 if [ -z "$(ls -1 ./fedora-release-${release}*.noarch.rpm 2>/dev/null)" ] ||
858 [ -z "$(ls -1 ./fedora-repos-${release}*.noarch.rpm 2>/dev/null)" ]
865 # copy packages to chroot file system
866 if [ -n "${CHROOT_DIR}" ]
868 cp .
/fedora-release-
${release}*.noarch.rpm
"${CHROOT_DIR}" &&
869 cp .
/fedora-repos-
${release}*.noarch.rpm
"${CHROOT_DIR}"
871 local pkgdir
="${cache}"
874 # use '--nodeps' to work around 'fedora-release-24-*' bash dependency
875 ${CHROOT_CMD}rpm --root "${INSTALL_ROOT}" -ivh --nodeps "${pkgdir}"/{fedora-release-${release}*.noarch.rpm,fedora-repos-${release}*.noarch.rpm}
878 # dnf will take $basearch from host, so force the arch we want
879 sed -i "s|\$basearch|${target_arch}|" ${CHROOT_DIR}${INSTALL_ROOT}/etc/yum.repos.d/*
883 if [ "${ret}" -ne 0 ]
885 echo "Failed to setup repositories
in ${CHROOT_DIR}${INSTALL_ROOT}"
889 # cleanup installed packages
890 if [ -n "${CHROOT_DIR}" ]
892 # shellcheck disable=SC2086
893 rm -f "${CHROOT_DIR}"/{fedora-release-${release}*.noarch.rpm,fedora-repos-${release}*.noarch.rpm}
899 # Run dnf update in the given chroot directory.
901 chroot_update_fedora
()
904 chroot_mounts
"${chroot}"
906 # always make sure /etc/resolv.conf is up to date in the target!
907 cp /etc
/resolv.conf
"${chroot}/etc/"
909 chroot
"${chroot}" dnf
-y update
912 sleep 3 # wait for all file handles to properly close
914 chroot_umounts
"${chroot}"
919 # Unmount installroot after bootstrapping or on error.
921 unmount_installroot
() {
922 if [ -n "${CHROOT_DIR}" ]
924 sleep 3 # wait for all file handles to properly close
925 chroot_umounts
"${CHROOT_DIR}" 2>/dev
/null
926 umount
"${CHROOT_DIR}${INSTALL_ROOT}" 2>/dev
/null
933 LXC Container configuration for Fedora images.
935 Template specific options can be passed to lxc-create after a '--' like this:
937 lxc-create --name=NAME -t fedora [OPTION..] -- [TEMPLATE_OPTION..]
941 -a, --arch Define what arch the container will be [i386,x86_64]
942 -c, --clean Clean bootstrap and download cache
943 -d, --debug Run with 'set -x' to debug errors
944 --fqdn Fully qualified domain name (FQDN)
945 -h, --help Print this help text
946 --mask-tmp Prevent systemd from over-mounting /tmp with tmpfs.
947 -M, --mirror=MIRROR Fedora mirror to use during installation.
948 -p, --path=PATH Path to where the container will be created,
949 defaults to ${lxc_path}.
950 -P, --packages=PKGS Comma-separated list of additional RPM packages to
951 install into the container.
952 -R, --release=RELEASE Fedora release number of the container, defaults
953 to host's release if the host is Fedora.
954 --rootfs=ROOTFS Path for the actual container root file system
955 --rsync Use rsync instead of HTTPS to download bootstrap
958 Environment variables:
960 LXC_CACHE_PATH Cache directory for image bootstrap. Defaults to
963 MIRRORLIST_URL List of Fedora mirrors queried if no custom mirror is
964 given. Defaults to '${MIRRORLIST_URL}'
966 FEDORA_RSYNC_URL Fedora rsync URL to use for bootstrap with '--rsync'.
967 Defaults to '${FEDORA_RSYNC_URL}'
969 FEDORA_RELEASE_DEFAULT Set default Fedora release if not detected from the
970 host. Is set to '${FEDORA_RELEASE_DEFAULT}'
976 options
=$
(getopt
-o a
:hp
:n
:cR
:dP
:M
: -l help,path
:,rootfs
:,name
:,clean
,release
:,arch
:,debug
,fqdn
:,mask-tmp
,mirror
:,packages
:,rsync
-- "$@")
977 # shellcheck disable=SC2181
978 if [ $?
-ne 0 ]; then
987 eval set -- "$options"
991 -h|
--help) usage
; exit 0 ;;
992 -n|
--name) name
="${2}"; shift 2 ;;
993 -p|
--path) path
="${2}"; shift 2 ;;
994 --rootfs) rootfs
="${2}"; shift 2 ;;
995 -a|
--arch) newarch
="${2}"; shift 2 ;;
996 -c|
--clean) clean
=1; shift 1 ;;
997 -d|
--debug) debug
=1; shift 1 ;;
998 --fqdn) utsname
="${2}"; shift 2 ;;
999 --mask-tmp) masktmp
=1; shift 1 ;;
1000 -M|
--mirror) mirror
="${2}"; shift 2 ;;
1001 -P|
--packages) packages
="${2}"; shift 2 ;;
1002 -R|
--release) release
="${2}"; shift 2 ;;
1003 --rsync) rsync
=1; shift 1 ;;
1004 --) shift 1; break ;;
1009 if [ "${debug}" -eq 1 ]
1014 # change to a safe directory
1018 # Allow for weird remixes like the Raspberry Pi
1020 # Use the Mitre standard CPE identifier for the release ID if possible...
1021 # This may be in /etc/os-release or /etc/system-release-cpe. We
1022 # should be able to use EITHER. Give preference to /etc/os-release for now.
1024 if [ -e /etc
/os-release
]
1026 # This is a shell friendly configuration file. We can just source it.
1027 # What we're looking for in here is the ID, VERSION_ID and the CPE_NAME
1031 if [ "${CPE_NAME}" = "" ] && [ -e /etc
/system-release-cpe
]
1033 CPE_NAME
=$
(head -n1 /etc
/system-release-cpe
)
1034 CPE_URI
=$
(expr "${CPE_NAME}" : '\([^:]*:[^:]*\)')
1035 if [ "${CPE_URI}" != "cpe:/o" ]
1039 echo "Host CPE ID from /etc/system-release-cpe: ${CPE_NAME}"
1040 # Probably a better way to do this but sill remain posix
1041 # compatible but this works, shrug...
1042 # Must be nice and not introduce convenient bashisms here.
1043 ID
=$
(expr ${CPE_NAME} : '[^:]*:[^:]*:[^:]*:\([^:]*\)')
1044 VERSION_ID
=$
(expr ${CPE_NAME} : '[^:]*:[^:]*:[^:]*:[^:]*:\([^:]*\)')
1048 if [ "${ID}" = "fedora" ] && [ -n "${CPE_NAME}" ] && [ -n "${VERSION_ID}" ]
1050 fedora_host_ver
=${VERSION_ID}
1054 # Map a few architectures to their generic Fedora repository archs.
1055 # The two ARM archs are a bit of a guesstimate for the v5 and v6
1056 # archs. V6 should have hardware floating point (Rasberry Pi).
1057 # The "arm" arch is safer (no hardware floating point). So
1058 # there may be cases where we "get it wrong" for some v6 other
1062 i686
) basearch
=i386
;;
1063 armv3l|armv4l|armv5l
) basearch
=arm
;;
1064 armv6l|armv7l|armv8l
) basearch
=armhfp
;;
1068 case "${basearch}" in
1069 ppc64|s390x
) FEDORA_RSYNC_URL
="archives.fedoraproject.org::fedora-secondary" ;;
1073 # Somebody wants to specify an arch. This is very limited case.
1074 # i386/i586/i686 on i386/x86_64
1077 if [ "${newarch}" != "" ] && [ "${newarch}" != "${arch}" ]
1079 case "${newarch}" in
1081 if [ "${basearch}" = "i386" ] ||
[ "${basearch}" = "x86_64" ]
1083 # Make the arch a generic x86 32 bit...
1094 if [ "${basearch}" = "bad" ]
1096 echo "Error: You cannot build a ${newarch} Fedora container on a ${arch} host. Sorry!"
1101 # Let's do something better for the initial root password.
1102 # It's not perfect but it will defeat common scanning brute force
1103 # attacks in the case where ssh is exposed. It will also be set to
1104 # expired, forcing the user to change it at first login.
1105 if [ "${root_password}" = "" ]
1107 root_password
=Root-
${name}-${RANDOM}
1109 # If it's got a ding in it, try and expand it!
1110 if [ "$(expr "${root_password}" : '.*$.')" != 0 ]
1112 root_password
=$
(eval echo "${root_password}")
1115 # If it has more than 3 consecutive X's in it, feed it
1116 # through mktemp as a template.
1117 if [ "$(expr "${root_password}" : '.*XXXX')" != 0 ]
1119 root_password
=$
(mktemp
-u "${root_password}")
1123 if [ -z "${utsname}" ]; then
1127 # This follows a standard "resolver" convention that an FQDN must have
1128 # at least two dots or it is considered a local relative host name.
1129 # If it doesn't, append the dns domain name of the host system.
1131 # This changes one significant behavior when running
1132 # "lxc_create -n Container_Name" without using the
1136 # utsname and hostname = Container_Name
1138 # utsname and hostname = Container_Name.Domain_Name
1140 if [ "$(expr "${utsname}" : '.*\..*\.')" = 0 ]
1142 if [ -n "$(dnsdomainname)" ] && [ "$(dnsdomainname)" != "localdomain" ]
1144 utsname
="${utsname}.$(dnsdomainname)"
1148 # check if the pre-requisite binaries are available
1149 prerequisite_pkgs
=( curl openssl rsync
)
1151 for pkg
in "${prerequisite_pkgs[@]}"
1153 if ! type "${pkg}" >/dev
/null
2>&1
1155 needed_pkgs
="${pkg} ${needed_pkgs}"
1158 if [ -n "${needed_pkgs}" ]
1160 echo "Error: Missing command(s): ${needed_pkgs}"
1164 if [ "$(id -u)" != "0" ]
1166 echo "This script should be run as 'root'"
1170 # cleanup cache if requested
1171 cache
="${LXC_CACHE_PATH}/fedora"
1172 if [ -n "${clean}" ]
1174 clean_cache
"${cache}" ||
exit 1
1178 # set container directory
1181 path
="${lxc_path}/${name}"
1184 # set container rootfs and configuration path
1185 config
="${path}/config"
1186 if [ -z "${rootfs}" ]
1188 # check for 'lxc.rootfs.path' passed in through default config by lxc-create
1189 if grep -q '^lxc.rootfs.path' "${config}" 2>/dev
/null
1191 rootfs
=$
(awk -F= '/^lxc.rootfs.path =/{ print $2 }' "${config}")
1193 rootfs
="${path}/rootfs"
1197 # set release if not given
1198 if [ -z "${release}" ]
1200 if [ "${is_fedora}" ] && [ -n "${fedora_host_ver}" ]
1202 echo "Detected Fedora ${fedora_host_ver} host. Set release to ${fedora_host_ver}."
1203 release
="${fedora_host_ver}"
1205 echo "This is not a Fedora host or release is missing, defaulting release to ${FEDORA_RELEASE_DEFAULT}."
1206 release
="${FEDORA_RELEASE_DEFAULT}"
1209 if [ "${release}" -lt "${FEDORA_RELEASE_MIN}" ]
1211 echo "Error: Fedora release ${release} is not supported. Set -R at least to ${FEDORA_RELEASE_MIN}."
1215 # bootstrap rootfs and copy to container file system
1216 if ! install_fedora
"${rootfs}" "${cache}"
1218 echo "Error: Failed to create Fedora container"
1222 # customize container file system
1223 if ! configure_fedora
"${rootfs}" "${release}" "${utsname}"
1225 echo "Error: Failed to configure Fedora container"
1229 # create container configuration (will be overwritten by newer lxc-create)
1230 if ! copy_configuration
"${rootfs}" "${config}" "${utsname}"
1232 echo "Error: Failed write container configuration file"
1236 if [ -n "${clean}" ]; then
1241 echo "Successfully created container '${name}'"
1243 if [ "${root_display_password}" = "yes" ]
1245 echo "The temporary password for root is: '$root_password'
1247 You may want to note that password down before starting the container.
1251 if [ "${root_store_password}" = "yes" ]
1253 echo "The temporary root password is stored in:
1255 '${config}/tmp_root_pass'
1259 if [ "${root_prompt_password}" = "yes" ]
1261 echo "Invoking the passwd command in the container to set the root password.
1263 chroot ${rootfs} passwd
1265 chroot
"${rootfs}" passwd
1267 if [ "${root_expire_password}" = "yes" ]
1269 if ( mountpoint
-q -- "${rootfs}" )
1271 echo "To reset the root password, you can do:
1273 lxc-start -n ${name}
1274 lxc-attach -n ${name} -- passwd
1279 The root password is set up as expired and will require it to be changed
1280 at first login, which you should do as soon as possible. If you lose the
1281 root password or wish to change it without starting the container, you
1282 can change it from the host by running the following command (which will
1283 also reset the expired flag):
1285 chroot ${rootfs} passwd
1291 # vim: set ts=4 sw=4 expandtab: