]> git.proxmox.com Git - mirror_lxc.git/blame - templates/lxc-fedora.in
Fix installing multiple packages given with '--packages'
[mirror_lxc.git] / templates / lxc-fedora.in
CommitLineData
3d7aa788
RG
1#!/bin/bash
2
3#
4# template script for generating Fedora container for LXC
5#
6
7#
8# lxc: linux Container library
9
10# Authors:
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>
15
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.
20
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.
25
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
29
30# configurations
31FEDORA_RELEASE_MIN=24
32FEDORA_RELEASE_DEFAULT=${FEDORA_RELEASE_DEFAULT:-25}
33FEDORA_RSYNC_URL="${FEDORA_RSYNC_URL:-archives.fedoraproject.org::fedora-enchilada}"
34MIRRORLIST_URL=${MIRRORLIST_URL:-http://mirrors.fedoraproject.org/mirrorlist}
35
36local_state_dir="@LOCALSTATEDIR@"
37lxc_path="@LXCPATH@"
38lxc_template_config="@LXCTEMPLATECONFIG@"
39lxc_default_conf="@LXC_DEFAULT_CONFIG@"
40
41# allows the cache directory to be set by environment variable
42LXC_CACHE_PATH="${LXC_CACHE_PATH:-"${local_state_dir}/cache/lxc"}"
43
44# these are only going into comments in the resulting config...
45lxc_network_type=veth
46lxc_network_link=lxcbr0
47
48# Some combinations of the tuning knobs below do not exactly make sense.
49# but that's ok.
50#
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.
54#
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.
59#
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.
66#
67# These are conditional assignments... The can be overridden from the
68# preexisting environment variables...
69#
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'}"
73
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'}"
81
82# Expire root password? Default to yes, but can be overridden from
83# the environment variable
84: "${root_expire_password='yes'}"
85
86# detect use under userns (unsupported)
87for arg in "$@"; do
88 [ "${arg}" = "--" ] && break
89 if [ "${arg}" = "--mapped-uid" ] || [ "${arg}" = "--mapped-gid" ]
90 then
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
93 exit 1
94 fi
95done
96
97# make sure the usual locations are in PATH
98export PATH=${PATH}:/usr/sbin:/usr/bin:/sbin:/bin
99
100# dnf package manager arguments
101dnf_args=( --assumeyes --best --allowerasing --disablerepo=* --enablerepo=fedora --enablerepo=updates )
102
103# This function is going to setup a minimal Fedora bootstrap environment
104# which will be used to create new containers on non-Fedora hosts.
105#
106bootstrap_fedora()
107{
108 local cache="${1}"
109
110 local bootstrap="${cache}/bootstrap"
111 if [ -d "${bootstrap}" ]
112 then
113 echo "Existing Fedora bootstrap environment found. Testing ..."
114 if chroot_update_fedora "${bootstrap}"
115 then
116 CHROOT_DIR="${bootstrap}"
117 CHROOT_CMD="chroot ${CHROOT_DIR} "
118
119 echo "Bootstrap environment appears to be functional."
120 return 0
121 else
122 echo "Error: Bootstrap environment detected in ${bootstrap}"
123 echo "but appears to be non-functional. Please remove and restart."
124 return 1
125 fi
126 fi
127
128 echo "Setting up new Fedora ${FEDORA_RELEASE_DEFAULT} (${arch}) bootstrap environment."
129
130 [[ -d "${cache}" ]] || mkdir -p "${cache}"
131
132 tmp_bootstrap_dir=$( mktemp -d --tmpdir="${cache}" bootstrap_XXXXXX )
133 pushd "${tmp_bootstrap_dir}" >/dev/null
134
135 mkdir squashfs liveos bootstrap
136
137 # download the LiveOS squashfs image
138 if [ ! -f "${cache}/install.img" ]
139 then
140
141 local image_path="/linux/releases/${FEDORA_RELEASE_DEFAULT}/Everything/${arch}/os/images/install.img"
142 local ret=1
143
144 if [ -n "${mirror}" ]
145 then
146 echo -n "Downloading LiveOS squashfs image from ${mirror} ... "
147 curl --silent --show-error --fail --remote-name "${mirror}${image_path}"
148 ret=$?
149 echo
150 else
151 echo "Syncing LiveOS squashfs image from ${FEDORA_RSYNC_URL} ... "
152 rsync --archive --info=progress "${FEDORA_RSYNC_URL}${image_path}" .
153 ret=$?
154 fi
155 if [ "${ret}" != 0 ] || [ ! -s install.img ]
156 then
157 echo "Error: Download of squashfs image failed."
158 return 1
159 fi
160 mv install.img "${cache}/"
161 else
162 echo "Using cached LiveOS squashfs image."
163 fi
164
165 echo "Mounting LiveOS squashfs file system."
df3e3fa1 166 if ! mount -o loop -t squashfs "${cache}/install.img" squashfs/
3d7aa788
RG
167 then
168 echo "
169Error: Mount of LiveOS squashfs image failed
170--------------------------------------------
171You must have squashfs support available to mount image. LiveOS image is now
172cached. Process may be rerun without penalty of downloading LiveOS again. If
173LiveOS is corrupt, remove ${cache}/install.img
174"
175 return 1
176 fi
177
178 # mount contained LiveOS
179 if ! mount -o loop squashfs/LiveOS/rootfs.img liveos
180 then
181 echo "
182Error: Mount of LiveOS stage0 rootfs image failed
183-------------------------------------------------
184LiveOS download may be corrupt. Remove ${cache}/LiveOS
185to force a new download.
186"
187 return 1
188 fi
189
190 echo "Copying LiveOS content to bootstrap environment ... "
191 if ! rsync --archive --acls --hard-links --sparse liveos/. bootstrap/
192 then
193 echo "Error: Build of bootstrap environment failed."
194 echo "Keeping directory ${tmp_bootstrap_dir} for your investigation."
195 exit 1
196 fi
197
198 # unmount liveos mounts - we're done with liveos at this point
199 umount liveos
200 umount squashfs
201
202 # customize bootstrap rootfs
203 pushd bootstrap >/dev/null
204
205 # setup repositories in bootstrap chroot
206 CHROOT_DIR="$(pwd)"
207 CHROOT_CMD="chroot ${CHROOT_DIR} "
208 INSTALL_ROOT="/"
209 if ! setup_repositories "${cache}" "${arch}" "${FEDORA_RELEASE_DEFAULT}" "${mirror}"
210 then
211 echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
212 exit 1
213 fi
214 if [ -n "${mirror}" ]
215 then
216 set_dnf_mirror_url ./etc/yum.repos.d/fedora*.repo
217 fi
218
219 # make sure /etc/resolv.conf is up to date in the chroot
220 cp /etc/resolv.conf ./etc/
221
222 # verify bootstrap chroot by running a dnf update
223 chroot_update_fedora "$(pwd)"
224 ret=$?
225
226 popd >/dev/null
227
228 if [ "${ret}" != 0 ]
229 then
230 echo "Error: Build of bootstrap environment failed."
231 echo "Keeping directory ${tmp_bootstrap_dir} for your investigation."
232 return 1
233 fi
234
235 mv bootstrap "${cache}"
236
237 popd >/dev/null
238 rm -rf "${tmp_bootstrap_dir}"
239
240 CHROOT_DIR="${bootstrap}"
241 CHROOT_CMD="chroot ${CHROOT_DIR} "
242
243 echo "Fedora bootstrap environment successfully created."
244 return 0
245}
246
247chroot_mounts()
248{
249 test -n "${1}" && local chroot="${1}" || return 1
250
251 mount -t proc proc "${chroot}/proc" &&
252 mount -o bind /dev "${chroot}/dev"
253}
254
255chroot_umounts()
256{
257 test -n "${1}" && local chroot="${1}" || return 1
258
259 umount "${chroot}/proc" &&
260 umount "${chroot}/dev"
261}
262
263clean_cache()
264{
265 local cache="${1}"
266
267 test ! -e "${cache}" && return 0
268
269 # lock, so we won't purge while someone is creating a repository
270 (
271 if ! flock -x 9
272 then
273 echo "Error: Cache repository is busy."
274 exit 1
275 fi
276
277 echo -n "Purging the Fedora bootstrap and download cache ... "
278 rm --preserve-root --one-file-system -rf "${cache}" && echo "Done." || exit 1
279
280 exit 0
281
282 ) 9>"${local_state_dir}/lock/subsys/lxc-fedora"
283
284 return $?
285}
286
287# Customize container rootfs
288#
289configure_fedora()
290{
291 local rootfs="${1}"
292 local release="${2}"
293 local utsname="${3}"
294
295 # disable selinux
296 mkdir -p "${rootfs}/selinux"
297 echo 0 > "${rootfs}/selinux/enforce"
298
299 # also kill it in the /etc/selinux/config file if it's there...
300 if [ -f "${rootfs}/etc/selinux/config" ]
301 then
302 sed -i '/^SELINUX=/s/.*/SELINUX=disabled/' "${rootfs}/etc/selinux/config"
303 fi
304
305 # nice catch from Dwight Engen in the Oracle template.
306 # wantonly plagerized here with much appreciation.
307 if [ -f "${rootfs}/usr/sbin/selinuxenabled" ]
308 then
309 rm -f "${rootfs}/usr/sbin/selinuxenabled"
310 ln -s /bin/false "${rootfs}/usr/sbin/selinuxenabled"
311 fi
312
313 # set hostname
314 echo "${utsname}" > "${rootfs}/etc/hostname"
315
316 # set default localtime to the host localtime if not set...
317 if [ -e /etc/localtime ] && [ ! -e "${rootfs}/etc/localtime" ]
318 then
319 # if /etc/localtime is a symlink, this should preserve it.
320 cp -a /etc/localtime "${rootfs}/etc/localtime"
321 fi
322
323 # set minimal hosts
324 cat <<EOF > "${rootfs}/etc/hosts"
325127.0.0.1 localhost.localdomain localhost ${utsname}
326::1 localhost6.localdomain6 localhost6
327EOF
328
329 # setup console and tty[1-4] for login. note that /dev/console and
330 # /dev/tty[1-4] will be symlinks to the ptys /dev/lxc/console and
331 # /dev/lxc/tty[1-4] so that package updates can overwrite the symlinks.
332 # lxc will maintain these links and bind mount ptys over /dev/lxc/*
333 # since lxc.devttydir is specified in the config.
334
335 # allow root login on console, tty[1-4], and pts/0 for libvirt
336 cat <<EOF >> "${rootfs}/etc/securetty"
337# LXC (Linux Containers)
338lxc/console
339lxc/tty1
340lxc/tty2
341lxc/tty3
342lxc/tty4
343# For libvirt/Virtual Machine Monitor
344pts/0
345EOF
346
347 if [ "${root_display_password}" = yes ]
348 then
349 echo "Setting root password to '$root_password'"
350 fi
351 if [ "${root_store_password}" = yes ]
352 then
353 touch "${path}/tmp_root_pass"
354 chmod 600 "${path}/tmp_root_pass"
355 echo "${root_password}" > "${path}/tmp_root_pass"
356 echo "Storing root password in '${path}/tmp_root_pass'"
357 fi
358
359 echo "root:$root_password" | chroot "${rootfs}" chpasswd
360
361 if [ "${root_expire_password}" = yes ]
362 then
363 # also set this password as expired to force the user to change it!
364 chroot "${rootfs}" passwd -e root
365 fi
366
367 chroot_mounts "${rootfs}"
368
369 # always make sure /etc/resolv.conf is up to date in the target!
370 cp /etc/resolv.conf "${rootfs}/etc/"
371
372 # rebuild the rpm database based on the target rpm version...
373 rm -f "${rootfs}"/var/lib/rpm/__db*
374 chroot "${rootfs}" rpm --rebuilddb
375
376 chroot_umounts "${rootfs}"
377
378 # default systemd target
379 chroot "${rootfs}" ln -s /lib/systemd/system/multi-user.target \
380 /etc/systemd/system/default.target
381
382 # enable networking via systemd-networkd
383 test -d "${rootfs}/etc/systemd/network" || mkdir "${rootfs}/etc/systemd/network"
384 cat <<EOF > "${rootfs}/etc/systemd/network/eth0.network"
385[Match]
386Name=eth0
387
388[Network]
389DHCP=both
390EOF
391 mkdir -p "${rootfs}/etc/systemd/system/socket.target.wants"
392 chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-networkd.socket \
393 /etc/systemd/system/socket.target.wants/systemd-networkd.socket
394 chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-networkd.service \
395 /etc/systemd/system/multi-user.target.wants/systemd-networkd.service
396 mkdir -p "${rootfs}/etc/systemd/system/network-online.target.wants"
397 chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-networkd-wait-online.service \
398 /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
399
400 # disable traditional network init
401 chroot "${rootfs}" chkconfig network off
402
403 # enable systemd-resolved
404 rm -f "${rootfs}/etc/resolv.conf"
405 chroot "${rootfs}" ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
406 chroot "${rootfs}" ln -s /usr/lib/systemd/system/systemd-resolved.service \
407 /etc/systemd/system/multi-user.target.wants/systemd-resolved.service
408
409 # if desired, prevent systemd from over-mounting /tmp with tmpfs
410 if [ "${masktmp}" -eq 1 ]
411 then
412 chroot "${rootfs}" ln -s /dev/null /etc/systemd/system/tmp.mount
413 fi
414
415 return 0
416}
417
418copy_configuration()
419{
420 local rootfs="${1}"
421 local config="${2}"
422 local utsname="${3}"
423
424 # include configuration from default.conf if available
425 grep -q "^lxc." "${lxc_default_conf}" > "${config}" 2>/dev/null
426
427 grep -q "^lxc.rootfs" "${config}" 2>/dev/null || echo "
428lxc.rootfs = ${rootfs}
429" >> "${config}"
430
431 # The following code is to create static MAC addresses for each
432 # interface in the container. This code will work for multiple
433 # interfaces in the default config. It will also strip any
434 # hwaddr stanzas out of the default config since we can not share
435 # MAC addresses between containers.
436 mv "${config}" "${config}.orig"
437
438 local line key
439 while read -r line
440 do
441 # This should catch variable expansions from the default config...
442 if expr "${line}" : '.*\$' > /dev/null 2>&1
443 then
444 line=$(eval "echo \"${line}\"")
445 fi
446
447 # There is a tab and a space in the regex bracket below!
448 # Seems that \s doesn't work in brackets.
449 key=$(expr "${line}" : '\s*\([^ ]*\)\s*=')
450
451 if [ "${key}" != "lxc.network.hwaddr" ]
452 then
453 echo "${line}" >> "${config}"
454
455 if [ "${key}" == "lxc.network.link" ]
456 then
457 echo "lxc.network.hwaddr = $(create_hwaddr)" >> "${config}"
458 fi
459 fi
460 done < "${config}.orig"
461 rm -f "${config}.orig"
462
463 if [ -e "${lxc_template_config}/fedora.common.conf" ]
464 then
465 echo "
466# Include common configuration
467lxc.include = ${lxc_template_config}/fedora.common.conf
468" >> "${config}"
469 fi
470
471 cat <<EOF >> "${path}/config"
472# Container specific configuration
473lxc.arch = ${basearch}
474lxc.utsname = ${utsname}
475
476# When using LXC with apparmor, uncomment the next line to run unconfined:
477#lxc.aa_profile = unconfined
478
479# example simple networking setup, uncomment to enable
480#lxc.network.type = ${lxc_network_type}
481#lxc.network.flags = up
482#lxc.network.link = ${lxc_network_link}
483#lxc.network.name = eth0
484# Additional example for veth network type
485# static MAC address,
486#lxc.network.hwaddr = $(create_hwaddr)
487# persistent veth device name on host side
488# Note: This may potentially collide with other containers of same name!
489#lxc.network.veth.pair = v-${name}-e0
490EOF
491
492 if [ $? -ne 0 ]
493 then
494 echo "Failed to add configuration"
495 return 1
496 fi
497
498 return 0
499}
500
501copy_fedora()
502{
503 local cache="${1}"
504 local rootfs="${2}"
505 echo -n "Copying ${cache} to ${rootfs} ... "
506
507 mkdir -p "${rootfs}" &&
508 rsync --archive --hard-links --sparse "${cache}/" "${rootfs}/" &&
509 echo || return 1
510
511 return 0
512}
513
514# Generate a random hardware (MAC) address composed of FE followed by
515# 5 random bytes...
516#
517create_hwaddr()
518{
519 openssl rand -hex 5 | sed -e 's/\(..\)/:\1/g; s/^/fe/'
520}
521
522# Make sure a fully functional rootfs of the requested release and architecture
523# will be setup in the given cache directory. If this is a Fedora host the
524# commands will run natively otherwise in a minimal Fedora bootstrap chroot.
525#
526download_fedora()
527{
528 local cache_rootfs="${1}"
529 local setup_rootfs="${cache_rootfs%%/rootfs}/partial"
530
531 # suppress errors due to unknown locales
532 LC_ALL=C
533
534 echo "Downloading ${basearch} rootfs for Fedora ${release} ..."
535
536 # The following variables are going to be overwritten if the rootfs setup
537 # is run in a separate boostrap environment (can not build natively).
538 # These are the defaults for the non-boostrap (native) mode.
539 CHROOT_DIR=
540 CHROOT_CMD=
541 INSTALL_ROOT=${setup_rootfs}
542
7b40d728 543 if [ ! "${is_fedora}" ] || [ "${fedora_host_ver}" -lt "${FEDORA_VERSION_MINIMAL}" ]
3d7aa788
RG
544 then
545 # if this is not a supported Fedora host, use minimal bootstrap chroot
546 echo "Non-Fedora host detected. Checking for bootstrap environment ... "
547 if ! bootstrap_fedora "${cache}"
548 then
549 echo "Error: Fedora Bootstrap setup failed"
550 return 1
551 fi
552 echo "Using bootstrap environment at ${CHROOT_DIR}"
553 fi
554
555 if ! mkdir -p "${setup_rootfs}"
556 then
557 echo "Error: Failed to create '${setup_rootfs}' directory."
558 return 1
559 fi
560
561 trap revert_rootfs SIGHUP SIGINT SIGTERM
562
563 mkdir -p "${setup_rootfs}/var/lib/rpm"
564
565 # if the setup is going to be run in a chroot, mount the related file systems
566 if [ -n "${CHROOT_DIR}" ]
567 then
568 chroot_mounts "${CHROOT_DIR}"
569
570 # make sure rootfs is available in bootstrap chroot
571 INSTALL_ROOT="/run/install"
572 test -d "${CHROOT_DIR}${INSTALL_ROOT}" || mkdir -p "${CHROOT_DIR}${INSTALL_ROOT}"
573 mount -o bind "${setup_rootfs}" "${CHROOT_DIR}${INSTALL_ROOT}"
574 fi
575
576 if ! setup_repositories "${cache}" "${basearch}" "${release}" "${mirror}"
577 then
578 echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
579 revert_rootfs >/dev/null
580 return 1
581 fi
582
583 # Unforunately <dnf-2.0 doesn't respect the repository configuration of the
584 # installroot but use the one from the host. This obviously doesn't work
585 # with a custom mirror or target architecture. Therefore a temporary dnf.conf
586 # is created and passed to the chroot command.
587 cat "${CHROOT_DIR}${INSTALL_ROOT}"/etc/yum.repos.d/{fedora,fedora-updates}.repo > "${CHROOT_DIR}${INSTALL_ROOT}/dnf.conf"
588 if [ -n "${mirror}" ]
589 then
590 set_dnf_mirror_url "${CHROOT_DIR}${INSTALL_ROOT}/dnf.conf"
591 fi
592
593 # install minimal container file system
594 local pkg_list="dnf initscripts passwd vim-minimal openssh-server openssh-clients dhclient rootfiles policycoreutils fedora-release fedora-repos"
595 if ! ${CHROOT_CMD}dnf --installroot "${INSTALL_ROOT}" \
596 --config="${INSTALL_ROOT}/dnf.conf" \
597 --releasever "${release}" \
598 ${dnf_args[@]} \
599 install ${pkg_list}
600 then
601 echo "Error: Failed to setup the rootfs in ${CHROOT_DIR}${INSTALL_ROOT}."
602 revert_rootfs >/dev/null
603 return 1
604 fi
605
606 unmount_installroot
607
608 # from now on we'll work in the new rootfs
609 chroot_mounts "${setup_rootfs}"
610
611 # It might happen, that the dnf used above will write an incompatible
612 # RPM database for the version running in the rootfs. Rebuild it.
613 echo "Fixing up RPM databases"
614 rm -f "${CHROOT_DIR}${INSTALL_ROOT}"/var/lib/rpm/__db*
615 chroot "${setup_rootfs}" rpm --rebuilddb
616
617 chroot_umounts "${setup_rootfs}"
618
619 # reset traps
620 trap SIGHUP
621 trap SIGINT
622 trap SIGTERM
623
624 # use generated rootfs as future cache
625 mv "${setup_rootfs}" "${cache_rootfs}"
626
627 echo "Download of Fedora rootfs complete."
628 return 0
629}
630
631# Install a functional Fedora rootfs into the container root
632#
633install_fedora()
634{
635 local rootfs="${1}"
636 local cache="${2}"
637 local cache_rootfs="${cache}/${release}-${basearch}/rootfs"
638
639 mkdir -p "${local_state_dir}/lock/subsys/"
640 (
641 if ! flock -x 9
642 then
643 echo "Error: Cache repository is busy."
644 return 1
645 fi
646
647 echo "Checking cache download in ${cache_rootfs} ... "
648 if [ ! -e "${cache_rootfs}" ]
649 then
650 if ! download_fedora "${cache_rootfs}"
651 then
652 echo "Error: Failed to download Fedora ${release} (${basearch})"
653 return 1
654 fi
655 else
656 echo "Cache found at ${cache_rootfs}. Updating ..."
657 if ! chroot_update_fedora "${cache_rootfs}"
658 then
659 echo "Failed to update cached rootfs, continuing with previously cached version."
660 else
661 echo "Fedora update finished."
662 fi
663 fi
664
665 trap revert_container SIGHUP SIGINT SIGTERM
666
667 if ! copy_fedora "${cache_rootfs}" "${rootfs}"
668 then
669 echo "Error: Failed to copy rootfs"
670 return 1
671 fi
672
673 chroot_mounts "${rootfs}"
674
675 # install additional user provided packages
676 if [ -n "${packages}" ]
677 then
678 # always make sure /etc/resolv.conf is up to date in the target!
679 cp /etc/resolv.conf "${rootfs}/etc/"
680
681 echo "Installing user requested RPMs: ${packages}"
c172e264 682 if ! chroot "${rootfs}" dnf install ${dnf_args[@]} $(tr ',' ' ' <<< "${packages}")
3d7aa788
RG
683 then
684 echo "Error: Installation of user provided packages failed."
685 echo "Cleaning up ... "
686 sleep 3 # wait for all file handles to properly close
687 chroot_umounts "${setup_rootfs}"
688 return 1
689 fi
690 fi
691
692 # cleanup dnf cache in new container
693 chroot "${rootfs}" dnf clean all
694
695 sleep 3 # wait for all file handles to properly close
696 chroot_umounts "${rootfs}"
697
698 return 0
699 ) 9>"${local_state_dir}/lock/subsys/lxc-fedora"
700
701 return $?
702}
703
704# Cleanup partial container
705#
706revert_container()
707{
708 echo "Interrupted, so cleaning up ..."
709 lxc-destroy -n "${name}" 2>/dev/null
710 # maybe was interrupted before copy config, try to prevent some mistakes
711 if [ -d "${path}" ] &&
712 [ "${path}" != "/" ] && [ "${path}" != "/tmp" ] && [ "${path}" != "/bin" ]
713 then
714 rm -rf "${path}"
715 fi
716 echo "Exiting ..."
717 exit 1
718}
719
720# Cleanup partial rootfs cache
721#
722revert_rootfs()
723{
724 echo "Interrupted, so cleaning up ..."
725 unmount_installroot
726 rm -rf "${setup_rootfs}"
727 echo "Exiting ..."
728 exit 1
729}
730
731# Set dnf repository mirror in given repo files
732#
733set_dnf_mirror_url()
734{
735 sed -i -e 's/^\(metalink=.*\)$/#\1/g' "${@}"
736 sed -i -e '/baseurl/ s|^#||g' "${@}"
737 sed -i -e "/baseurl/ s|http://download.fedoraproject.org/pub/fedora|${mirror}|g" "${@}"
738}
739
740# Setup dnf repository configuration. It can be run in a chroot by specifying
741# $CHROOT_DIR (chroot directory) and $CHROOT_CMD (chroot command) and/or
742# with an alternative RPM install root defined in $INSTALL_ROOT.
743#
744setup_repositories()
745{
746 local cache="${1}"
747 local target_arch="${2}"
748 local release="${3}"
749 local mirror="${4}"
750
751 # download repository packages if not found in cache
752 pushd "${cache}" >/dev/null
753 if [ -z "$(ls -1 ./fedora-release-${release}*.noarch.rpm 2>/dev/null)" ] ||
754 [ -z "$(ls -1 ./fedora-repos-${release}*.noarch.rpm 2>/dev/null)" ]
755 then
756 # if no mirror given, get an appropriate mirror from the mirror list
757 if [ -z "${mirror}" ]
758 then
759 for trynumber in 1 2 3 4
760 do
761 [ "${trynumber}" != 1 ] && echo -n "Trying again ... "
762
763 # choose some mirrors by parsing directory index
764 mirror_urls=$(curl --silent --show-error --fail "${MIRRORLIST_URL}?repo=fedora-${release}&arch=${target_arch}" | sed -e '/^http:/!d' -e '2,6!d')
765
766 # shellcheck disable=SC2181
767 if [ $? -eq 0 ] && [ -n "${mirror_urls}" ]
768 then
769 break
770 fi
771
772 echo "Warning: Failed to get a mirror on try ${trynumber}."
773 sleep 3
774 done
775 else
776 # construct release-specific mirror url
777 mirror="${mirror}/linux/releases/${release}/Everything/${target_arch}/os"
778 fi
779
780 # this will fall through if we didn't get any mirrors
781 for mirror_url in ${mirror:-${mirror_urls}}
782 do
783 local release_url="${mirror_url}/Packages/f"
784
785 for pkg in fedora-release-${release} fedora-repos-${release}
786 do
787 test -n "$(ls -1 ./${pkg}*.noarch.rpm 2>/dev/null)" && continue
788
789 # query package name by parsing directory index
790 echo "Requesting '${pkg}' package version from ${release_url}."
791 pkg_name=$(curl --silent --show-error --fail "${release_url}/" | sed -n -e "/${pkg}/ s/.*href=\"\(${pkg}-.*\.noarch\.rpm\)\">.*/\1/p" | tail -1)
792
793 # shellcheck disable=SC2181
794 if [ $? -ne 0 ] || [ -z "${pkg_name}" ]
795 then
796 echo "Error: Failed to get '${pkg}' version from ${release_url}/."
797 continue
798 fi
799
800 echo "Downloading '${release_url}/${pkg_name} ... "
801 if ! curl --silent --show-error --fail --remote-name "${release_url}/${pkg_name}"
802 then
803 echo "Error: Package download failed."
804 continue
805 fi
806 done
807
808 # if we have both packages continue
809 if [ -z "$(ls -1 ./fedora-release-${release}*.noarch.rpm 2>/dev/null)" ] ||
810 [ -z "$(ls -1 ./fedora-repos-${release}*.noarch.rpm 2>/dev/null)" ]
811 then
812 break
813 fi
814 done
815 fi
816
817 # copy packages to chroot file system
818 if [ -n "${CHROOT_DIR}" ]
819 then
820 cp ./fedora-release-${release}*.noarch.rpm "${CHROOT_DIR}" &&
821 cp ./fedora-repos-${release}*.noarch.rpm "${CHROOT_DIR}"
822 else
823 local pkgdir="${cache}"
824 fi
825
826 # use '--nodeps' to work around 'fedora-release-24-*' bash dependency
827 ${CHROOT_CMD}rpm --root "${INSTALL_ROOT}" -ivh --nodeps "${pkgdir}"/{fedora-release-${release}*.noarch.rpm,fedora-repos-${release}*.noarch.rpm}
828 local ret=$?
829
830 # dnf will take $basearch from host, so force the arch we want
831 sed -i "s|\$basearch|${target_arch}|" ${CHROOT_DIR}${INSTALL_ROOT}/etc/yum.repos.d/*
832
833 popd >/dev/null
834
835 if [ "${ret}" -ne 0 ]
836 then
837 echo "Failed to setup repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
838 exit 1
839 fi
840
841 # cleanup installed packages
842 if [ -n "${CHROOT_DIR}" ]
843 then
844 # shellcheck disable=SC2086
845 rm -f "${CHROOT_DIR}"/{fedora-release-${release}*.noarch.rpm,fedora-repos-${release}*.noarch.rpm}
846 fi
847
848 return 0
849}
850
851# Run dnf update in the given chroot directory.
852#
853chroot_update_fedora()
854{
855 local chroot="${1}"
856 chroot_mounts "${chroot}"
857
858 # always make sure /etc/resolv.conf is up to date in the target!
859 cp /etc/resolv.conf "${chroot}/etc/"
860
861 chroot "${chroot}" dnf -y update
862 local ret=$?
863
864 sleep 3 # wait for all file handles to properly close
865
866 chroot_umounts "${chroot}"
867
868 return ${ret}
869}
870
871# Unmount installroot after bootstrapping or on error.
872#
873unmount_installroot() {
874 if [ -n "${CHROOT_DIR}" ]
875 then
876 sleep 3 # wait for all file handles to properly close
877 chroot_umounts "${CHROOT_DIR}" 2>/dev/null
878 umount "${CHROOT_DIR}${INSTALL_ROOT}" 2>/dev/null
879 fi
880}
881
882usage()
883{
884 cat <<EOF
885LXC Container configuration for Fedora images.
886
887Template specific options can be passed to lxc-create after a '--' like this:
888
889 lxc-create --name=NAME -t fedora [OPTION..] -- [TEMPLATE_OPTION..]
890
891Template options:
892
893 -a, --arch Define what arch the container will be [i686,x86_64]
894 -c, --clean Clean bootstrap and download cache
895 -d, --debug Run with 'set -x' to debug errors
896 --fqdn Fully qualified domain name (FQDN)
897 -h, --help Print this help text
898 --mask-tmp Prevent systemd from over-mounting /tmp with tmpfs.
899 --mirror=MIRROR Fedora mirror to use during installation. Overrides the
900 FEDORA_RSYNC_URL environment variable (see below).
901 -p, --path=PATH Path to where the container will be created,
902 defaults to ${lxc_path}.
903 -P, --packages=PKGS Comma-separated list of additional RPM packages to
904 install into the container.
905 -R, --release=RELEASE Fedora release number of the container, defaults
906 to host's release if the host is Fedora.
907 --rootfs=ROOTFS Path for the actual container root file system
908
909Environment variables:
910
911 LXC_CACHE_PATH Cache directory for image bootstrap. Defaults to
912 '${LXC_CACHE_PATH}'
913
914 MIRRORLIST_URL List of Fedora mirrors queried if no custom mirror is
915 given. Defaults to '${MIRRORLIST_URL}'
916
917 FEDORA_RSYNC_URL Fedora rsync mirror to use for bootstrap setup.
918 Defaults to '${FEDORA_RSYNC_URL}'
919
920 FEDORA_RELEASE_DEFAULT Set default Fedora release if not detected from the
921 host. Is set to '${FEDORA_RELEASE_DEFAULT}'
922
923EOF
924 return 0
925}
926
927options=$(getopt -o a:hp:n:cR:dP: -l help,path:,rootfs:,name:,clean,release:,arch:,debug,fqdn:,mask-tmp,mirror:,packages: -- "$@")
928# shellcheck disable=SC2181
929if [ $? -ne 0 ]; then
930 usage
931 exit 1
932fi
933
934arch=$(uname -m)
935debug=0
936masktmp=0
937
938eval set -- "$options"
939while true
940do
941 case "${1}" in
942 -h|--help) usage; exit 0 ;;
943 -n|--name) name="${2}"; shift 2 ;;
944 -p|--path) path="${2}"; shift 2 ;;
945 --rootfs) rootfs="${2}"; shift 2 ;;
946 -a|--arch) newarch="${2}"; shift 2 ;;
947 -c|--clean) clean=1; shift 1 ;;
948 -d|--debug) debug=1; shift 1 ;;
949 --fqdn) utsname="${2}"; shift 2 ;;
950 --mask-tmp) masktmp=1; shift 1 ;;
951 --mirror) mirror="${2}"; shift 2 ;;
952 -P|--packages) packages="${2}"; shift 2 ;;
953 -R|--release) release="${2}"; shift 2 ;;
954 --) shift 1; break ;;
955 *) break ;;
956 esac
957done
958
959if [ "${debug}" -eq 1 ]
960then
961 set -x
962fi
963
964# change to a safe directory
965cd || exit $?
966
967# Is this Fedora?
968# Allow for weird remixes like the Raspberry Pi
969#
970# Use the Mitre standard CPE identifier for the release ID if possible...
971# This may be in /etc/os-release or /etc/system-release-cpe. We
972# should be able to use EITHER. Give preference to /etc/os-release for now.
973
974if [ -e /etc/os-release ]
975then
976# This is a shell friendly configuration file. We can just source it.
977# What we're looking for in here is the ID, VERSION_ID and the CPE_NAME
978 . /etc/os-release
979fi
980
981if [ "${CPE_NAME}" = "" ] && [ -e /etc/system-release-cpe ]
982then
983 CPE_NAME=$(head -n1 /etc/system-release-cpe)
984 CPE_URI=$(expr "${CPE_NAME}" : '\([^:]*:[^:]*\)')
985 if [ "${CPE_URI}" != "cpe:/o" ]
986 then
987 CPE_NAME=
988 else
989 echo "Host CPE ID from /etc/system-release-cpe: ${CPE_NAME}"
990 # Probably a better way to do this but sill remain posix
991 # compatible but this works, shrug...
992 # Must be nice and not introduce convenient bashisms here.
993 ID=$(expr ${CPE_NAME} : '[^:]*:[^:]*:[^:]*:\([^:]*\)')
994 VERSION_ID=$(expr ${CPE_NAME} : '[^:]*:[^:]*:[^:]*:[^:]*:\([^:]*\)')
995 fi
996fi
997
998if [ "${ID}" = "fedora" ] && [ -n "${CPE_NAME}" ] && [ -n "${VERSION_ID}" ]
999then
1000 fedora_host_ver=${VERSION_ID}
1001 is_fedora=true
1002fi
1003
1004basearch=${arch}
1005# Map a few architectures to their generic Fedora repository archs.
1006# The two ARM archs are a bit of a guesstimate for the v5 and v6
1007# archs. V6 should have hardware floating point (Rasberry Pi).
1008# The "arm" arch is safer (no hardware floating point). So
1009# there may be cases where we "get it wrong" for some v6 other
1010# than RPi.
1011case "$arch" in
1012i686) basearch=i386 ;;
1013armv3l|armv4l|armv5l) basearch=arm ;;
1014armv6l|armv7l|armv8l) basearch=armhfp ;;
1015*) ;;
1016esac
1017
1018case "${basearch}" in
1019 ppc64|s390x) FEDORA_RSYNC_URL="archives.fedoraproject.org::fedora-secondary" ;;
1020 *) ;;
1021esac
1022
1023# Somebody wants to specify an arch. This is very limited case.
1024# i386/i586/i686 on i386/x86_64
1025# - or -
1026# x86_64 on x86_64
1027if [ "${newarch}" != "" ] && [ "${newarch}" != "${arch}" ]
1028then
1029 case "${newarch}" in
1030 i386|i586|i686)
1031 if [ "${basearch}" = "i386" ] || [ "${basearch}" = "x86_64" ]
1032 then
1033 # Make the arch a generic x86 32 bit...
1034 basearch=i386
1035 else
1036 basearch=bad
1037 fi
1038 ;;
1039 *)
1040 basearch=bad
1041 ;;
1042 esac
1043
1044 if [ "${basearch}" = "bad" ]
1045 then
1046 echo "Error: You cannot build a ${newarch} Fedora container on a ${arch} host. Sorry!"
1047 exit 1
1048 fi
1049fi
1050
1051# Let's do something better for the initial root password.
1052# It's not perfect but it will defeat common scanning brute force
1053# attacks in the case where ssh is exposed. It will also be set to
1054# expired, forcing the user to change it at first login.
1055if [ "${root_password}" = "" ]
1056then
1057 root_password=Root-${name}-${RANDOM}
1058else
1059 # If it's got a ding in it, try and expand it!
1060 if [ "$(expr "${root_password}" : '.*$.')" != 0 ]
1061 then
1062 root_password=$(eval echo "${root_password}")
1063 fi
1064
1065 # If it has more than 3 consecutive X's in it, feed it
1066 # through mktemp as a template.
1067 if [ "$(expr "${root_password}" : '.*XXXX')" != 0 ]
1068 then
1069 root_password=$(mktemp -u "${root_password}")
1070 fi
1071fi
1072
1073if [ -z "${utsname}" ]; then
1074 utsname=${name}
1075fi
1076
1077# This follows a standard "resolver" convention that an FQDN must have
1078# at least two dots or it is considered a local relative host name.
1079# If it doesn't, append the dns domain name of the host system.
1080#
1081# This changes one significant behavior when running
1082# "lxc_create -n Container_Name" without using the
1083# --fqdn option.
1084#
1085# Old behavior:
1086# utsname and hostname = Container_Name
1087# New behavior:
1088# utsname and hostname = Container_Name.Domain_Name
1089
1090if [ "$(expr "${utsname}" : '.*\..*\.')" = 0 ]
1091then
1092 if [ -n "$(dnsdomainname)" ] && [ "$(dnsdomainname)" != "localdomain" ]
1093 then
1094 utsname="${utsname}.$(dnsdomainname)"
1095 fi
1096fi
1097
1098# check if the pre-requisite binaries are available
1099prerequisite_pkgs=( curl openssl rsync )
1100needed_pkgs=""
1101for pkg in "${prerequisite_pkgs[@]}"
1102do
1103 if ! type "${pkg}" >/dev/null 2>&1
1104 then
1105 needed_pkgs="${pkg} ${needed_pkgs}"
1106 fi
1107done
1108if [ -n "${needed_pkgs}" ]
1109then
1110 echo "Error: Missing command(s): ${needed_pkgs}"
1111 exit 1
1112fi
1113
1114if [ "$(id -u)" != "0" ]
1115then
1116 echo "This script should be run as 'root'"
1117 exit 1
1118fi
1119
1120# cleanup cache if requested
1121cache="${LXC_CACHE_PATH}/fedora"
1122if [ -n "${clean}" ]
1123then
1124 clean_cache "${cache}" || exit 1
1125 exit 0
1126fi
1127
1128# set container directory
1129if [ -z "${path}" ]
1130then
1131 path="${lxc_path}/${name}"
1132fi
1133
1134# set container rootfs and configuration path
1135config="${path}/config"
1136if [ -z "${rootfs}" ]
1137then
1138 # check for 'lxc.rootfs' passed in through default config by lxc-create
1139 if grep -q '^lxc.rootfs' "${config}" 2>/dev/null
1140 then
1141 rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' "${config}")
1142 else
1143 rootfs="${path}/rootfs"
1144 fi
1145fi
1146
1147# set release if not given
1148if [ -z "${release}" ]
1149then
1150 if [ "${is_fedora}" ] && [ -n "${fedora_host_ver}" ]
1151 then
1152 echo "Detected Fedora ${fedora_host_ver} host. Set release to ${fedora_host_ver}."
1153 release="${fedora_host_ver}"
1154 else
1155 echo "This is not a Fedora host or release is missing, defaulting release to ${FEDORA_RELEASE_DEFAULT}."
1156 release="${FEDORA_RELEASE_DEFAULT}"
1157 fi
1158fi
1159if [ "${release}" -lt "${FEDORA_RELEASE_MIN}" ]
1160then
1161 echo "Error: Fedora release ${release} is not supported. Set -R at least to ${FEDORA_RELEASE_MIN}."
1162 exit 1
1163fi
1164
1165# bootstrap rootfs and copy to container file system
1166if ! install_fedora "${rootfs}" "${cache}"
1167then
1168 echo "Error: Failed to create Fedora container"
1169 exit 1
1170fi
1171
1172# customize container file system
1173if ! configure_fedora "${rootfs}" "${release}" "${utsname}"
1174then
1175 echo "Error: Failed to configure Fedora container"
1176 exit 1
1177fi
1178
1179# create container configuration (will be overwritten by newer lxc-create)
1180if ! copy_configuration "${rootfs}" "${config}" "${utsname}"
1181then
1182 echo "Error: Failed write container configuration file"
1183 exit 1
1184fi
1185
1186if [ -n "${clean}" ]; then
1187 clean || exit 1
1188 exit 0
1189fi
1190
1191echo "Successfully created container '${name}'"
1192
1193if [ "${root_display_password}" = "yes" ]
1194then
1195 echo "The temporary password for root is: '$root_password'
1196
1197You may want to note that password down before starting the container.
1198"
1199fi
1200
1201if [ "${root_store_password}" = "yes" ]
1202then
1203 echo "The temporary root password is stored in:
1204
1205 '${config}/tmp_root_pass'
1206"
1207fi
1208
1209if [ "${root_prompt_password}" = "yes" ]
1210then
1211 echo "Invoking the passwd command in the container to set the root password.
1212
1213 chroot ${rootfs} passwd
1214"
1215 chroot "${rootfs}" passwd
1216else
1217 if [ "${root_expire_password}" = "yes" ]
1218 then
1219 if ( mountpoint -q -- "${rootfs}" )
1220 then
1221 echo "To reset the root password, you can do:
1222
1223 lxc-start -n ${name}
1224 lxc-attach -n ${name} -- passwd
1225 lxc-stop -n ${name}
1226"
1227 else
1228 echo "
1229The root password is set up as expired and will require it to be changed
1230at first login, which you should do as soon as possible. If you lose the
1231root password or wish to change it without starting the container, you
1232can change it from the host by running the following command (which will
1233also reset the expired flag):
1234
1235 chroot ${rootfs} passwd
1236"
1237 fi
1238 fi
1239fi
1240
1241# vim: set ts=4 sw=4 expandtab: