]> git.proxmox.com Git - mirror_lxc.git/blame - templates/lxc-fedora.in
lxc-oci: rely on jq instead of sed to transform values
[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}"
52c4c368 34MIRRORLIST_URL=${MIRRORLIST_URL:-https://mirrors.fedoraproject.org/mirrorlist}
3d7aa788
RG
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
c898497d
RG
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.
3d7aa788
RG
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
509140b0 128 echo "Setting up new Fedora ${FEDORA_RELEASE_DEFAULT} (${basearch}) bootstrap environment."
3d7aa788
RG
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
509140b0
RG
140 local os_path="linux/releases/${FEDORA_RELEASE_DEFAULT}/Everything/${basearch}/os"
141 local image_path="images/install.img"
3d7aa788
RG
142 local ret=1
143
577eb5e3 144 if [ -n "${rsync}" ]
3d7aa788 145 then
3d7aa788 146 echo "Syncing LiveOS squashfs image from ${FEDORA_RSYNC_URL} ... "
509140b0 147 rsync --archive --info=progress "${FEDORA_RSYNC_URL}/${os_path}/${image_path}" .
3d7aa788 148 ret=$?
577eb5e3
RG
149 else
150 if [ -z "${mirror}" ]
151 then
509140b0
RG
152 get_mirrors "${FEDORA_RELEASE_DEFAULT}" "${basearch}" || return $?
153 else
154 local mirror_url="${mirror}/${os_path}"
577eb5e3 155 fi
509140b0 156 for url in ${mirror:-${mirror_urls}}
577eb5e3
RG
157 do
158 echo "Downloading LiveOS squashfs image from ${url} ... "
509140b0
RG
159 curl --silent --show-error --fail --remote-name "${url}/${image_path}"
160 ret=$?
161 if [ ${ret} -ne 0 ]
577eb5e3 162 then
577eb5e3 163 continue
509140b0
RG
164 else
165 break
577eb5e3 166 fi
577eb5e3 167 done
3d7aa788 168 fi
577eb5e3 169
3d7aa788
RG
170 if [ "${ret}" != 0 ] || [ ! -s install.img ]
171 then
172 echo "Error: Download of squashfs image failed."
173 return 1
174 fi
175 mv install.img "${cache}/"
176 else
177 echo "Using cached LiveOS squashfs image."
178 fi
179
180 echo "Mounting LiveOS squashfs file system."
df3e3fa1 181 if ! mount -o loop -t squashfs "${cache}/install.img" squashfs/
3d7aa788
RG
182 then
183 echo "
184Error: Mount of LiveOS squashfs image failed
185--------------------------------------------
186You must have squashfs support available to mount image. LiveOS image is now
187cached. Process may be rerun without penalty of downloading LiveOS again. If
188LiveOS is corrupt, remove ${cache}/install.img
189"
190 return 1
191 fi
192
193 # mount contained LiveOS
194 if ! mount -o loop squashfs/LiveOS/rootfs.img liveos
195 then
196 echo "
197Error: Mount of LiveOS stage0 rootfs image failed
198-------------------------------------------------
199LiveOS download may be corrupt. Remove ${cache}/LiveOS
200to force a new download.
201"
202 return 1
203 fi
204
205 echo "Copying LiveOS content to bootstrap environment ... "
206 if ! rsync --archive --acls --hard-links --sparse liveos/. bootstrap/
207 then
208 echo "Error: Build of bootstrap environment failed."
209 echo "Keeping directory ${tmp_bootstrap_dir} for your investigation."
210 exit 1
211 fi
212
213 # unmount liveos mounts - we're done with liveos at this point
214 umount liveos
215 umount squashfs
216
217 # customize bootstrap rootfs
218 pushd bootstrap >/dev/null
219
220 # setup repositories in bootstrap chroot
221 CHROOT_DIR="$(pwd)"
222 CHROOT_CMD="chroot ${CHROOT_DIR} "
223 INSTALL_ROOT="/"
e8672a9d 224 if ! setup_repositories "${cache}" "${basearch}" "${FEDORA_RELEASE_DEFAULT}" "${mirror}"
3d7aa788
RG
225 then
226 echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
227 exit 1
228 fi
229 if [ -n "${mirror}" ]
230 then
231 set_dnf_mirror_url ./etc/yum.repos.d/fedora*.repo
232 fi
233
234 # make sure /etc/resolv.conf is up to date in the chroot
235 cp /etc/resolv.conf ./etc/
236
237 # verify bootstrap chroot by running a dnf update
238 chroot_update_fedora "$(pwd)"
239 ret=$?
240
241 popd >/dev/null
242
243 if [ "${ret}" != 0 ]
244 then
245 echo "Error: Build of bootstrap environment failed."
246 echo "Keeping directory ${tmp_bootstrap_dir} for your investigation."
247 return 1
248 fi
249
250 mv bootstrap "${cache}"
251
252 popd >/dev/null
253 rm -rf "${tmp_bootstrap_dir}"
254
255 CHROOT_DIR="${bootstrap}"
256 CHROOT_CMD="chroot ${CHROOT_DIR} "
257
258 echo "Fedora bootstrap environment successfully created."
259 return 0
260}
261
262chroot_mounts()
263{
264 test -n "${1}" && local chroot="${1}" || return 1
265
266 mount -t proc proc "${chroot}/proc" &&
267 mount -o bind /dev "${chroot}/dev"
268}
269
270chroot_umounts()
271{
272 test -n "${1}" && local chroot="${1}" || return 1
273
274 umount "${chroot}/proc" &&
275 umount "${chroot}/dev"
276}
277
278clean_cache()
279{
280 local cache="${1}"
281
282 test ! -e "${cache}" && return 0
283
284 # lock, so we won't purge while someone is creating a repository
285 (
286 if ! flock -x 9
287 then
288 echo "Error: Cache repository is busy."
289 exit 1
290 fi
291
292 echo -n "Purging the Fedora bootstrap and download cache ... "
293 rm --preserve-root --one-file-system -rf "${cache}" && echo "Done." || exit 1
294
295 exit 0
296
297 ) 9>"${local_state_dir}/lock/subsys/lxc-fedora"
298
299 return $?
300}
301
302# Customize container rootfs
303#
304configure_fedora()
305{
306 local rootfs="${1}"
307 local release="${2}"
308 local utsname="${3}"
309
310 # disable selinux
311 mkdir -p "${rootfs}/selinux"
312 echo 0 > "${rootfs}/selinux/enforce"
313
314 # also kill it in the /etc/selinux/config file if it's there...
315 if [ -f "${rootfs}/etc/selinux/config" ]
316 then
317 sed -i '/^SELINUX=/s/.*/SELINUX=disabled/' "${rootfs}/etc/selinux/config"
318 fi
319
320 # nice catch from Dwight Engen in the Oracle template.
321 # wantonly plagerized here with much appreciation.
322 if [ -f "${rootfs}/usr/sbin/selinuxenabled" ]
323 then
324 rm -f "${rootfs}/usr/sbin/selinuxenabled"
325 ln -s /bin/false "${rootfs}/usr/sbin/selinuxenabled"
326 fi
327
328 # set hostname
329 echo "${utsname}" > "${rootfs}/etc/hostname"
330
331 # set default localtime to the host localtime if not set...
332 if [ -e /etc/localtime ] && [ ! -e "${rootfs}/etc/localtime" ]
333 then
334 # if /etc/localtime is a symlink, this should preserve it.
335 cp -a /etc/localtime "${rootfs}/etc/localtime"
336 fi
337
338 # set minimal hosts
339 cat <<EOF > "${rootfs}/etc/hosts"
340127.0.0.1 localhost.localdomain localhost ${utsname}
341::1 localhost6.localdomain6 localhost6
342EOF
343
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/*
42e53c29 348 # since lxc.tty.dir is specified in the config.
3d7aa788
RG
349
350 # allow root login on console, tty[1-4], and pts/0 for libvirt
351 cat <<EOF >> "${rootfs}/etc/securetty"
352# LXC (Linux Containers)
353lxc/console
354lxc/tty1
355lxc/tty2
356lxc/tty3
357lxc/tty4
358# For libvirt/Virtual Machine Monitor
359pts/0
360EOF
361
362 if [ "${root_display_password}" = yes ]
363 then
364 echo "Setting root password to '$root_password'"
365 fi
366 if [ "${root_store_password}" = yes ]
367 then
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'"
372 fi
373
374 echo "root:$root_password" | chroot "${rootfs}" chpasswd
375
376 if [ "${root_expire_password}" = yes ]
377 then
378 # also set this password as expired to force the user to change it!
379 chroot "${rootfs}" passwd -e root
380 fi
381
382 chroot_mounts "${rootfs}"
383
384 # always make sure /etc/resolv.conf is up to date in the target!
385 cp /etc/resolv.conf "${rootfs}/etc/"
386
387 # rebuild the rpm database based on the target rpm version...
388 rm -f "${rootfs}"/var/lib/rpm/__db*
389 chroot "${rootfs}" rpm --rebuilddb
390
391 chroot_umounts "${rootfs}"
392
393 # default systemd target
394 chroot "${rootfs}" ln -s /lib/systemd/system/multi-user.target \
395 /etc/systemd/system/default.target
396
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"
400[Match]
401Name=eth0
402
403[Network]
404DHCP=both
405EOF
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
414
415 # disable traditional network init
416 chroot "${rootfs}" chkconfig network off
417
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
423
424 # if desired, prevent systemd from over-mounting /tmp with tmpfs
425 if [ "${masktmp}" -eq 1 ]
426 then
427 chroot "${rootfs}" ln -s /dev/null /etc/systemd/system/tmp.mount
428 fi
429
430 return 0
431}
432
433copy_configuration()
434{
435 local rootfs="${1}"
436 local config="${2}"
437 local utsname="${3}"
438
439 # include configuration from default.conf if available
440 grep -q "^lxc." "${lxc_default_conf}" > "${config}" 2>/dev/null
441
7a96a068
CB
442 grep -q "^lxc.rootfs.path" "${config}" 2>/dev/null || echo "
443lxc.rootfs.path = ${rootfs}
3d7aa788
RG
444" >> "${config}"
445
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"
452
453 local line key
454 while read -r line
455 do
456 # This should catch variable expansions from the default config...
457 if expr "${line}" : '.*\$' > /dev/null 2>&1
458 then
459 line=$(eval "echo \"${line}\"")
460 fi
461
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*=')
465
7fa3f2e9 466 if [ "${key}" != "lxc.net.0.hwaddr" ]
3d7aa788
RG
467 then
468 echo "${line}" >> "${config}"
469
7fa3f2e9 470 if [ "${key}" == "lxc.net.0.link" ]
3d7aa788 471 then
7fa3f2e9 472 echo "lxc.net.0.hwaddr = $(create_hwaddr)" >> "${config}"
3d7aa788
RG
473 fi
474 fi
475 done < "${config}.orig"
476 rm -f "${config}.orig"
477
478 if [ -e "${lxc_template_config}/fedora.common.conf" ]
479 then
480 echo "
481# Include common configuration
482lxc.include = ${lxc_template_config}/fedora.common.conf
483" >> "${config}"
484 fi
485
486 cat <<EOF >> "${path}/config"
487# Container specific configuration
488lxc.arch = ${basearch}
b67771bc 489lxc.uts.name = ${utsname}
3d7aa788
RG
490
491# When using LXC with apparmor, uncomment the next line to run unconfined:
a1d5fdfd 492#lxc.apparmor.profile = unconfined
3d7aa788
RG
493
494# example simple networking setup, uncomment to enable
7fa3f2e9 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
3d7aa788
RG
499# Additional example for veth network type
500# static MAC address,
7fa3f2e9 501#lxc.net.0.hwaddr = $(create_hwaddr)
3d7aa788
RG
502# persistent veth device name on host side
503# Note: This may potentially collide with other containers of same name!
7fa3f2e9 504#lxc.net.0.veth.pair = v-${name}-e0
3d7aa788
RG
505EOF
506
507 if [ $? -ne 0 ]
508 then
509 echo "Failed to add configuration"
510 return 1
511 fi
512
513 return 0
514}
515
516copy_fedora()
517{
518 local cache="${1}"
519 local rootfs="${2}"
520 echo -n "Copying ${cache} to ${rootfs} ... "
521
522 mkdir -p "${rootfs}" &&
6273aef1 523 rsync --archive --hard-links --sparse --acls --xattrs "${cache}/" "${rootfs}/" &&
3d7aa788
RG
524 echo || return 1
525
526 return 0
527}
528
529# Generate a random hardware (MAC) address composed of FE followed by
530# 5 random bytes...
531#
532create_hwaddr()
533{
534 openssl rand -hex 5 | sed -e 's/\(..\)/:\1/g; s/^/fe/'
535}
536
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.
540#
541download_fedora()
542{
543 local cache_rootfs="${1}"
544 local setup_rootfs="${cache_rootfs%%/rootfs}/partial"
545
546 # suppress errors due to unknown locales
547 LC_ALL=C
91a5f346 548 LANG=en_US
3d7aa788
RG
549
550 echo "Downloading ${basearch} rootfs for Fedora ${release} ..."
551
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.
555 CHROOT_DIR=
556 CHROOT_CMD=
557 INSTALL_ROOT=${setup_rootfs}
558
7b40d728 559 if [ ! "${is_fedora}" ] || [ "${fedora_host_ver}" -lt "${FEDORA_VERSION_MINIMAL}" ]
3d7aa788
RG
560 then
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}"
564 then
565 echo "Error: Fedora Bootstrap setup failed"
566 return 1
567 fi
568 echo "Using bootstrap environment at ${CHROOT_DIR}"
569 fi
570
571 if ! mkdir -p "${setup_rootfs}"
572 then
573 echo "Error: Failed to create '${setup_rootfs}' directory."
574 return 1
575 fi
576
577 trap revert_rootfs SIGHUP SIGINT SIGTERM
578
579 mkdir -p "${setup_rootfs}/var/lib/rpm"
580
581 # if the setup is going to be run in a chroot, mount the related file systems
582 if [ -n "${CHROOT_DIR}" ]
583 then
584 chroot_mounts "${CHROOT_DIR}"
585
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}"
590 fi
591
592 if ! setup_repositories "${cache}" "${basearch}" "${release}" "${mirror}"
593 then
594 echo "Error: Failed to configure repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
595 revert_rootfs >/dev/null
596 return 1
597 fi
598
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}" ]
605 then
606 set_dnf_mirror_url "${CHROOT_DIR}${INSTALL_ROOT}/dnf.conf"
607 fi
608
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}" \
614 ${dnf_args[@]} \
615 install ${pkg_list}
616 then
617 echo "Error: Failed to setup the rootfs in ${CHROOT_DIR}${INSTALL_ROOT}."
618 revert_rootfs >/dev/null
619 return 1
620 fi
621
622 unmount_installroot
623
624 # from now on we'll work in the new rootfs
625 chroot_mounts "${setup_rootfs}"
626
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"
f919f5ca 630 rm -f "${setup_rootfs}"/var/lib/rpm/__db*
3d7aa788
RG
631 chroot "${setup_rootfs}" rpm --rebuilddb
632
91a5f346
RG
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 {} +
638
639 chroot "${setup_rootfs}" localedef --list-archive | grep -v ^"${LANG}" | xargs \
640 chroot "${setup_rootfs}" localedef --delete-from-archive
641
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
645
646 echo "%_install_langs C:en:${LANG}:${LANG}.UTF-8" > "${setup_rootfs}/etc/rpm/macros.image-language-conf"
647
3d7aa788
RG
648 chroot_umounts "${setup_rootfs}"
649
650 # reset traps
651 trap SIGHUP
652 trap SIGINT
653 trap SIGTERM
654
655 # use generated rootfs as future cache
656 mv "${setup_rootfs}" "${cache_rootfs}"
657
658 echo "Download of Fedora rootfs complete."
659 return 0
660}
661
577eb5e3
RG
662# Query the Fedora mirrorlist for several HTTPS mirrors
663#
664get_mirrors()
665{
509140b0
RG
666 local release="${1}"
667 local mirror_arch="${2}"
c898497d 668
577eb5e3
RG
669 for trynumber in 1 2 3 4
670 do
671 [ "${trynumber}" != 1 ] && echo -n "Trying again ... "
672
673 # choose some mirrors by parsing directory index
c898497d 674 mirror_urls=$(curl --silent --show-error --fail "${MIRRORLIST_URL}?repo=fedora-${release}&arch=${mirror_arch}" | sed '/^https:/!d' | sed '2,6!d')
577eb5e3
RG
675
676 # shellcheck disable=SC2181
677 if [ $? -eq 0 ] && [ -n "${mirror_urls}" ]
678 then
679 break
680 fi
681
682 echo "Warning: Failed to get a mirror on try ${trynumber}."
683 sleep 3
684 done
685
686 if [ -z "${mirror_urls}" ]
687 then
688 echo "Error: Failed to retrieve Fedora mirror URL. Please use '-m MIRROR' option."
689 return 1
690 fi
691
692 return 0
693}
694
3d7aa788
RG
695# Install a functional Fedora rootfs into the container root
696#
697install_fedora()
698{
699 local rootfs="${1}"
700 local cache="${2}"
701 local cache_rootfs="${cache}/${release}-${basearch}/rootfs"
702
703 mkdir -p "${local_state_dir}/lock/subsys/"
704 (
705 if ! flock -x 9
706 then
707 echo "Error: Cache repository is busy."
708 return 1
709 fi
710
711 echo "Checking cache download in ${cache_rootfs} ... "
712 if [ ! -e "${cache_rootfs}" ]
713 then
714 if ! download_fedora "${cache_rootfs}"
715 then
716 echo "Error: Failed to download Fedora ${release} (${basearch})"
717 return 1
718 fi
719 else
720 echo "Cache found at ${cache_rootfs}. Updating ..."
721 if ! chroot_update_fedora "${cache_rootfs}"
722 then
723 echo "Failed to update cached rootfs, continuing with previously cached version."
724 else
725 echo "Fedora update finished."
726 fi
727 fi
728
729 trap revert_container SIGHUP SIGINT SIGTERM
730
731 if ! copy_fedora "${cache_rootfs}" "${rootfs}"
732 then
733 echo "Error: Failed to copy rootfs"
734 return 1
735 fi
736
737 chroot_mounts "${rootfs}"
738
739 # install additional user provided packages
740 if [ -n "${packages}" ]
741 then
742 # always make sure /etc/resolv.conf is up to date in the target!
743 cp /etc/resolv.conf "${rootfs}/etc/"
744
745 echo "Installing user requested RPMs: ${packages}"
c172e264 746 if ! chroot "${rootfs}" dnf install ${dnf_args[@]} $(tr ',' ' ' <<< "${packages}")
3d7aa788
RG
747 then
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}"
752 return 1
753 fi
754 fi
755
756 # cleanup dnf cache in new container
757 chroot "${rootfs}" dnf clean all
758
759 sleep 3 # wait for all file handles to properly close
760 chroot_umounts "${rootfs}"
761
762 return 0
763 ) 9>"${local_state_dir}/lock/subsys/lxc-fedora"
764
765 return $?
766}
767
768# Cleanup partial container
769#
770revert_container()
771{
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" ]
777 then
778 rm -rf "${path}"
779 fi
780 echo "Exiting ..."
781 exit 1
782}
783
784# Cleanup partial rootfs cache
785#
786revert_rootfs()
787{
788 echo "Interrupted, so cleaning up ..."
789 unmount_installroot
790 rm -rf "${setup_rootfs}"
791 echo "Exiting ..."
792 exit 1
793}
794
795# Set dnf repository mirror in given repo files
796#
797set_dnf_mirror_url()
798{
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" "${@}"
802}
803
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.
807#
808setup_repositories()
809{
810 local cache="${1}"
811 local target_arch="${2}"
812 local release="${3}"
813 local mirror="${4}"
814
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)" ]
819 then
820 # if no mirror given, get an appropriate mirror from the mirror list
821 if [ -z "${mirror}" ]
822 then
e93dfa9c
RG
823 get_mirrors "${release}" "${target_arch}" || return $?
824 else
825 # construct release-specific mirror url
826 mirror="${mirror}/linux/releases/${release}/Everything/${target_arch}/os"
827 fi
828
829 for mirror_url in ${mirror:-${mirror_urls}}
830 do
3d7aa788
RG
831 local release_url="${mirror_url}/Packages/f"
832
833 for pkg in fedora-release-${release} fedora-repos-${release}
834 do
835 test -n "$(ls -1 ./${pkg}*.noarch.rpm 2>/dev/null)" && continue
836
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)
840
841 # shellcheck disable=SC2181
842 if [ $? -ne 0 ] || [ -z "${pkg_name}" ]
843 then
844 echo "Error: Failed to get '${pkg}' version from ${release_url}/."
845 continue
846 fi
847
848 echo "Downloading '${release_url}/${pkg_name} ... "
849 if ! curl --silent --show-error --fail --remote-name "${release_url}/${pkg_name}"
850 then
851 echo "Error: Package download failed."
852 continue
853 fi
854 done
855
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)" ]
859 then
860 break
861 fi
862 done
863 fi
864
865 # copy packages to chroot file system
866 if [ -n "${CHROOT_DIR}" ]
867 then
868 cp ./fedora-release-${release}*.noarch.rpm "${CHROOT_DIR}" &&
869 cp ./fedora-repos-${release}*.noarch.rpm "${CHROOT_DIR}"
870 else
871 local pkgdir="${cache}"
872 fi
873
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}
876 local ret=$?
877
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/*
880
881 popd >/dev/null
882
883 if [ "${ret}" -ne 0 ]
884 then
885 echo "Failed to setup repositories in ${CHROOT_DIR}${INSTALL_ROOT}"
886 exit 1
887 fi
888
889 # cleanup installed packages
890 if [ -n "${CHROOT_DIR}" ]
891 then
892 # shellcheck disable=SC2086
893 rm -f "${CHROOT_DIR}"/{fedora-release-${release}*.noarch.rpm,fedora-repos-${release}*.noarch.rpm}
894 fi
895
896 return 0
897}
898
899# Run dnf update in the given chroot directory.
900#
901chroot_update_fedora()
902{
903 local chroot="${1}"
904 chroot_mounts "${chroot}"
905
906 # always make sure /etc/resolv.conf is up to date in the target!
907 cp /etc/resolv.conf "${chroot}/etc/"
908
909 chroot "${chroot}" dnf -y update
910 local ret=$?
911
912 sleep 3 # wait for all file handles to properly close
913
914 chroot_umounts "${chroot}"
915
916 return ${ret}
917}
918
919# Unmount installroot after bootstrapping or on error.
920#
921unmount_installroot() {
922 if [ -n "${CHROOT_DIR}" ]
923 then
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
927 fi
928}
929
930usage()
931{
932 cat <<EOF
933LXC Container configuration for Fedora images.
934
935Template specific options can be passed to lxc-create after a '--' like this:
936
937 lxc-create --name=NAME -t fedora [OPTION..] -- [TEMPLATE_OPTION..]
938
939Template options:
940
e8672a9d 941 -a, --arch Define what arch the container will be [i386,x86_64]
3d7aa788
RG
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.
3256fa17 947 -M, --mirror=MIRROR Fedora mirror to use during installation.
3d7aa788
RG
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
577eb5e3
RG
955 --rsync Use rsync instead of HTTPS to download bootstrap
956 image (insecure).
3d7aa788
RG
957
958Environment variables:
959
960 LXC_CACHE_PATH Cache directory for image bootstrap. Defaults to
961 '${LXC_CACHE_PATH}'
962
963 MIRRORLIST_URL List of Fedora mirrors queried if no custom mirror is
964 given. Defaults to '${MIRRORLIST_URL}'
965
577eb5e3 966 FEDORA_RSYNC_URL Fedora rsync URL to use for bootstrap with '--rsync'.
3d7aa788
RG
967 Defaults to '${FEDORA_RSYNC_URL}'
968
969 FEDORA_RELEASE_DEFAULT Set default Fedora release if not detected from the
970 host. Is set to '${FEDORA_RELEASE_DEFAULT}'
971
972EOF
973 return 0
974}
975
3256fa17 976options=$(getopt -o a:hp:n:cR:dP:M: -l help,path:,rootfs:,name:,clean,release:,arch:,debug,fqdn:,mask-tmp,mirror:,packages:,rsync -- "$@")
3d7aa788
RG
977# shellcheck disable=SC2181
978if [ $? -ne 0 ]; then
979 usage
980 exit 1
981fi
982
983arch=$(uname -m)
984debug=0
985masktmp=0
986
987eval set -- "$options"
988while true
989do
990 case "${1}" in
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 ;;
3256fa17 1000 -M|--mirror) mirror="${2}"; shift 2 ;;
3d7aa788
RG
1001 -P|--packages) packages="${2}"; shift 2 ;;
1002 -R|--release) release="${2}"; shift 2 ;;
577eb5e3 1003 --rsync) rsync=1; shift 1 ;;
3d7aa788
RG
1004 --) shift 1; break ;;
1005 *) break ;;
1006 esac
1007done
1008
1009if [ "${debug}" -eq 1 ]
1010then
1011 set -x
1012fi
1013
1014# change to a safe directory
1015cd || exit $?
1016
1017# Is this Fedora?
1018# Allow for weird remixes like the Raspberry Pi
1019#
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.
1023
1024if [ -e /etc/os-release ]
1025then
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
1028 . /etc/os-release
1029fi
1030
1031if [ "${CPE_NAME}" = "" ] && [ -e /etc/system-release-cpe ]
1032then
1033 CPE_NAME=$(head -n1 /etc/system-release-cpe)
1034 CPE_URI=$(expr "${CPE_NAME}" : '\([^:]*:[^:]*\)')
1035 if [ "${CPE_URI}" != "cpe:/o" ]
1036 then
1037 CPE_NAME=
1038 else
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} : '[^:]*:[^:]*:[^:]*:[^:]*:\([^:]*\)')
1045 fi
1046fi
1047
1048if [ "${ID}" = "fedora" ] && [ -n "${CPE_NAME}" ] && [ -n "${VERSION_ID}" ]
1049then
1050 fedora_host_ver=${VERSION_ID}
1051 is_fedora=true
1052fi
1053
3d7aa788
RG
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
1059# than RPi.
c898497d
RG
1060basearch=${arch}
1061case "${arch}" in
1062 i686) basearch=i386 ;;
1063 armv3l|armv4l|armv5l) basearch=arm ;;
1064 armv6l|armv7l|armv8l) basearch=armhfp ;;
1065 *) ;;
3d7aa788
RG
1066esac
1067
1068case "${basearch}" in
1069 ppc64|s390x) FEDORA_RSYNC_URL="archives.fedoraproject.org::fedora-secondary" ;;
1070 *) ;;
1071esac
1072
1073# Somebody wants to specify an arch. This is very limited case.
1074# i386/i586/i686 on i386/x86_64
1075# - or -
1076# x86_64 on x86_64
1077if [ "${newarch}" != "" ] && [ "${newarch}" != "${arch}" ]
1078then
1079 case "${newarch}" in
1080 i386|i586|i686)
1081 if [ "${basearch}" = "i386" ] || [ "${basearch}" = "x86_64" ]
1082 then
1083 # Make the arch a generic x86 32 bit...
1084 basearch=i386
1085 else
1086 basearch=bad
1087 fi
1088 ;;
1089 *)
1090 basearch=bad
1091 ;;
1092 esac
1093
1094 if [ "${basearch}" = "bad" ]
1095 then
1096 echo "Error: You cannot build a ${newarch} Fedora container on a ${arch} host. Sorry!"
1097 exit 1
1098 fi
1099fi
1100
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.
1105if [ "${root_password}" = "" ]
1106then
1107 root_password=Root-${name}-${RANDOM}
1108else
1109 # If it's got a ding in it, try and expand it!
1110 if [ "$(expr "${root_password}" : '.*$.')" != 0 ]
1111 then
1112 root_password=$(eval echo "${root_password}")
1113 fi
1114
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 ]
1118 then
1119 root_password=$(mktemp -u "${root_password}")
1120 fi
1121fi
1122
1123if [ -z "${utsname}" ]; then
1124 utsname=${name}
1125fi
1126
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.
1130#
1131# This changes one significant behavior when running
1132# "lxc_create -n Container_Name" without using the
1133# --fqdn option.
1134#
1135# Old behavior:
1136# utsname and hostname = Container_Name
1137# New behavior:
1138# utsname and hostname = Container_Name.Domain_Name
1139
1140if [ "$(expr "${utsname}" : '.*\..*\.')" = 0 ]
1141then
1142 if [ -n "$(dnsdomainname)" ] && [ "$(dnsdomainname)" != "localdomain" ]
1143 then
1144 utsname="${utsname}.$(dnsdomainname)"
1145 fi
1146fi
1147
1148# check if the pre-requisite binaries are available
1149prerequisite_pkgs=( curl openssl rsync )
1150needed_pkgs=""
1151for pkg in "${prerequisite_pkgs[@]}"
1152do
1153 if ! type "${pkg}" >/dev/null 2>&1
1154 then
1155 needed_pkgs="${pkg} ${needed_pkgs}"
1156 fi
1157done
1158if [ -n "${needed_pkgs}" ]
1159then
1160 echo "Error: Missing command(s): ${needed_pkgs}"
1161 exit 1
1162fi
1163
1164if [ "$(id -u)" != "0" ]
1165then
1166 echo "This script should be run as 'root'"
1167 exit 1
1168fi
1169
1170# cleanup cache if requested
1171cache="${LXC_CACHE_PATH}/fedora"
1172if [ -n "${clean}" ]
1173then
1174 clean_cache "${cache}" || exit 1
1175 exit 0
1176fi
1177
1178# set container directory
1179if [ -z "${path}" ]
1180then
1181 path="${lxc_path}/${name}"
1182fi
1183
1184# set container rootfs and configuration path
1185config="${path}/config"
1186if [ -z "${rootfs}" ]
1187then
7a96a068
CB
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
3d7aa788 1190 then
7a96a068 1191 rootfs=$(awk -F= '/^lxc.rootfs.path =/{ print $2 }' "${config}")
3d7aa788
RG
1192 else
1193 rootfs="${path}/rootfs"
1194 fi
1195fi
1196
1197# set release if not given
1198if [ -z "${release}" ]
1199then
1200 if [ "${is_fedora}" ] && [ -n "${fedora_host_ver}" ]
1201 then
1202 echo "Detected Fedora ${fedora_host_ver} host. Set release to ${fedora_host_ver}."
1203 release="${fedora_host_ver}"
1204 else
1205 echo "This is not a Fedora host or release is missing, defaulting release to ${FEDORA_RELEASE_DEFAULT}."
1206 release="${FEDORA_RELEASE_DEFAULT}"
1207 fi
1208fi
1209if [ "${release}" -lt "${FEDORA_RELEASE_MIN}" ]
1210then
1211 echo "Error: Fedora release ${release} is not supported. Set -R at least to ${FEDORA_RELEASE_MIN}."
1212 exit 1
1213fi
1214
1215# bootstrap rootfs and copy to container file system
1216if ! install_fedora "${rootfs}" "${cache}"
1217then
1218 echo "Error: Failed to create Fedora container"
1219 exit 1
1220fi
1221
1222# customize container file system
1223if ! configure_fedora "${rootfs}" "${release}" "${utsname}"
1224then
1225 echo "Error: Failed to configure Fedora container"
1226 exit 1
1227fi
1228
1229# create container configuration (will be overwritten by newer lxc-create)
1230if ! copy_configuration "${rootfs}" "${config}" "${utsname}"
1231then
1232 echo "Error: Failed write container configuration file"
1233 exit 1
1234fi
1235
1236if [ -n "${clean}" ]; then
1237 clean || exit 1
1238 exit 0
1239fi
1240
1241echo "Successfully created container '${name}'"
1242
1243if [ "${root_display_password}" = "yes" ]
1244then
1245 echo "The temporary password for root is: '$root_password'
1246
1247You may want to note that password down before starting the container.
1248"
1249fi
1250
1251if [ "${root_store_password}" = "yes" ]
1252then
1253 echo "The temporary root password is stored in:
1254
1255 '${config}/tmp_root_pass'
1256"
1257fi
1258
1259if [ "${root_prompt_password}" = "yes" ]
1260then
1261 echo "Invoking the passwd command in the container to set the root password.
1262
1263 chroot ${rootfs} passwd
1264"
1265 chroot "${rootfs}" passwd
1266else
1267 if [ "${root_expire_password}" = "yes" ]
1268 then
1269 if ( mountpoint -q -- "${rootfs}" )
1270 then
1271 echo "To reset the root password, you can do:
1272
1273 lxc-start -n ${name}
1274 lxc-attach -n ${name} -- passwd
1275 lxc-stop -n ${name}
1276"
1277 else
1278 echo "
1279The root password is set up as expired and will require it to be changed
1280at first login, which you should do as soon as possible. If you lose the
1281root password or wish to change it without starting the container, you
1282can change it from the host by running the following command (which will
1283also reset the expired flag):
1284
1285 chroot ${rootfs} passwd
1286"
1287 fi
1288 fi
1289fi
1290
1291# vim: set ts=4 sw=4 expandtab: