]>
Commit | Line | Data |
---|---|---|
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 | |
31 | FEDORA_RELEASE_MIN=24 | |
32 | FEDORA_RELEASE_DEFAULT=${FEDORA_RELEASE_DEFAULT:-25} | |
33 | FEDORA_RSYNC_URL="${FEDORA_RSYNC_URL:-archives.fedoraproject.org::fedora-enchilada}" | |
52c4c368 | 34 | MIRRORLIST_URL=${MIRRORLIST_URL:-https://mirrors.fedoraproject.org/mirrorlist} |
3d7aa788 RG |
35 | |
36 | local_state_dir="@LOCALSTATEDIR@" | |
37 | lxc_path="@LXCPATH@" | |
38 | lxc_template_config="@LXCTEMPLATECONFIG@" | |
39 | lxc_default_conf="@LXC_DEFAULT_CONFIG@" | |
40 | ||
41 | # allows the cache directory to be set by environment variable | |
42 | LXC_CACHE_PATH="${LXC_CACHE_PATH:-"${local_state_dir}/cache/lxc"}" | |
43 | ||
44 | # these are only going into comments in the resulting config... | |
45 | lxc_network_type=veth | |
46 | lxc_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) | |
87 | for 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 | |
95 | done | |
96 | ||
97 | # make sure the usual locations are in PATH | |
98 | export PATH=${PATH}:/usr/sbin:/usr/bin:/sbin:/bin | |
99 | ||
100 | # dnf package manager arguments | |
101 | dnf_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 | # |
106 | bootstrap_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 " | |
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 | |
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 " | |
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. | |
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 | ||
262 | chroot_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 | ||
270 | chroot_umounts() | |
271 | { | |
272 | test -n "${1}" && local chroot="${1}" || return 1 | |
273 | ||
274 | umount "${chroot}/proc" && | |
275 | umount "${chroot}/dev" | |
276 | } | |
277 | ||
278 | clean_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 | # | |
304 | configure_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" | |
340 | 127.0.0.1 localhost.localdomain localhost ${utsname} | |
341 | ::1 localhost6.localdomain6 localhost6 | |
342 | EOF | |
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) | |
353 | lxc/console | |
354 | lxc/tty1 | |
355 | lxc/tty2 | |
356 | lxc/tty3 | |
357 | lxc/tty4 | |
358 | # For libvirt/Virtual Machine Monitor | |
359 | pts/0 | |
360 | EOF | |
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] | |
401 | Name=eth0 | |
402 | ||
403 | [Network] | |
404 | DHCP=both | |
405 | EOF | |
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 | ||
433 | copy_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 " |
443 | lxc.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 | |
482 | lxc.include = ${lxc_template_config}/fedora.common.conf | |
483 | " >> "${config}" | |
484 | fi | |
485 | ||
486 | cat <<EOF >> "${path}/config" | |
487 | # Container specific configuration | |
488 | lxc.arch = ${basearch} | |
b67771bc | 489 | lxc.uts.name = ${utsname} |
3d7aa788 RG |
490 | |
491 | # When using LXC with apparmor, uncomment the next line to run unconfined: | |
492 | #lxc.aa_profile = unconfined | |
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 |
505 | EOF |
506 | ||
507 | if [ $? -ne 0 ] | |
508 | then | |
509 | echo "Failed to add configuration" | |
510 | return 1 | |
511 | fi | |
512 | ||
513 | return 0 | |
514 | } | |
515 | ||
516 | copy_fedora() | |
517 | { | |
518 | local cache="${1}" | |
519 | local rootfs="${2}" | |
520 | echo -n "Copying ${cache} to ${rootfs} ... " | |
521 | ||
522 | mkdir -p "${rootfs}" && | |
523 | rsync --archive --hard-links --sparse "${cache}/" "${rootfs}/" && | |
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 | # | |
532 | create_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 | # | |
541 | download_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 | # | |
664 | get_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 | # | |
697 | install_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 | # | |
770 | revert_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 | # | |
786 | revert_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 | # | |
797 | set_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 | # | |
808 | setup_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 | # | |
901 | chroot_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 | # | |
921 | unmount_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 | ||
930 | usage() | |
931 | { | |
932 | cat <<EOF | |
933 | LXC Container configuration for Fedora images. | |
934 | ||
935 | Template specific options can be passed to lxc-create after a '--' like this: | |
936 | ||
937 | lxc-create --name=NAME -t fedora [OPTION..] -- [TEMPLATE_OPTION..] | |
938 | ||
939 | Template 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 | |
958 | Environment 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 | ||
972 | EOF | |
973 | return 0 | |
974 | } | |
975 | ||
3256fa17 | 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 -- "$@") |
3d7aa788 RG |
977 | # shellcheck disable=SC2181 |
978 | if [ $? -ne 0 ]; then | |
979 | usage | |
980 | exit 1 | |
981 | fi | |
982 | ||
983 | arch=$(uname -m) | |
984 | debug=0 | |
985 | masktmp=0 | |
986 | ||
987 | eval set -- "$options" | |
988 | while true | |
989 | do | |
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 | |
1007 | done | |
1008 | ||
1009 | if [ "${debug}" -eq 1 ] | |
1010 | then | |
1011 | set -x | |
1012 | fi | |
1013 | ||
1014 | # change to a safe directory | |
1015 | cd || 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 | ||
1024 | if [ -e /etc/os-release ] | |
1025 | then | |
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 | |
1029 | fi | |
1030 | ||
1031 | if [ "${CPE_NAME}" = "" ] && [ -e /etc/system-release-cpe ] | |
1032 | then | |
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 | |
1046 | fi | |
1047 | ||
1048 | if [ "${ID}" = "fedora" ] && [ -n "${CPE_NAME}" ] && [ -n "${VERSION_ID}" ] | |
1049 | then | |
1050 | fedora_host_ver=${VERSION_ID} | |
1051 | is_fedora=true | |
1052 | fi | |
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 |
1060 | basearch=${arch} |
1061 | case "${arch}" in | |
1062 | i686) basearch=i386 ;; | |
1063 | armv3l|armv4l|armv5l) basearch=arm ;; | |
1064 | armv6l|armv7l|armv8l) basearch=armhfp ;; | |
1065 | *) ;; | |
3d7aa788 RG |
1066 | esac |
1067 | ||
1068 | case "${basearch}" in | |
1069 | ppc64|s390x) FEDORA_RSYNC_URL="archives.fedoraproject.org::fedora-secondary" ;; | |
1070 | *) ;; | |
1071 | esac | |
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 | |
1077 | if [ "${newarch}" != "" ] && [ "${newarch}" != "${arch}" ] | |
1078 | then | |
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 | |
1099 | fi | |
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. | |
1105 | if [ "${root_password}" = "" ] | |
1106 | then | |
1107 | root_password=Root-${name}-${RANDOM} | |
1108 | else | |
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 | |
1121 | fi | |
1122 | ||
1123 | if [ -z "${utsname}" ]; then | |
1124 | utsname=${name} | |
1125 | fi | |
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 | ||
1140 | if [ "$(expr "${utsname}" : '.*\..*\.')" = 0 ] | |
1141 | then | |
1142 | if [ -n "$(dnsdomainname)" ] && [ "$(dnsdomainname)" != "localdomain" ] | |
1143 | then | |
1144 | utsname="${utsname}.$(dnsdomainname)" | |
1145 | fi | |
1146 | fi | |
1147 | ||
1148 | # check if the pre-requisite binaries are available | |
1149 | prerequisite_pkgs=( curl openssl rsync ) | |
1150 | needed_pkgs="" | |
1151 | for pkg in "${prerequisite_pkgs[@]}" | |
1152 | do | |
1153 | if ! type "${pkg}" >/dev/null 2>&1 | |
1154 | then | |
1155 | needed_pkgs="${pkg} ${needed_pkgs}" | |
1156 | fi | |
1157 | done | |
1158 | if [ -n "${needed_pkgs}" ] | |
1159 | then | |
1160 | echo "Error: Missing command(s): ${needed_pkgs}" | |
1161 | exit 1 | |
1162 | fi | |
1163 | ||
1164 | if [ "$(id -u)" != "0" ] | |
1165 | then | |
1166 | echo "This script should be run as 'root'" | |
1167 | exit 1 | |
1168 | fi | |
1169 | ||
1170 | # cleanup cache if requested | |
1171 | cache="${LXC_CACHE_PATH}/fedora" | |
1172 | if [ -n "${clean}" ] | |
1173 | then | |
1174 | clean_cache "${cache}" || exit 1 | |
1175 | exit 0 | |
1176 | fi | |
1177 | ||
1178 | # set container directory | |
1179 | if [ -z "${path}" ] | |
1180 | then | |
1181 | path="${lxc_path}/${name}" | |
1182 | fi | |
1183 | ||
1184 | # set container rootfs and configuration path | |
1185 | config="${path}/config" | |
1186 | if [ -z "${rootfs}" ] | |
1187 | then | |
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 | |
1195 | fi | |
1196 | ||
1197 | # set release if not given | |
1198 | if [ -z "${release}" ] | |
1199 | then | |
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 | |
1208 | fi | |
1209 | if [ "${release}" -lt "${FEDORA_RELEASE_MIN}" ] | |
1210 | then | |
1211 | echo "Error: Fedora release ${release} is not supported. Set -R at least to ${FEDORA_RELEASE_MIN}." | |
1212 | exit 1 | |
1213 | fi | |
1214 | ||
1215 | # bootstrap rootfs and copy to container file system | |
1216 | if ! install_fedora "${rootfs}" "${cache}" | |
1217 | then | |
1218 | echo "Error: Failed to create Fedora container" | |
1219 | exit 1 | |
1220 | fi | |
1221 | ||
1222 | # customize container file system | |
1223 | if ! configure_fedora "${rootfs}" "${release}" "${utsname}" | |
1224 | then | |
1225 | echo "Error: Failed to configure Fedora container" | |
1226 | exit 1 | |
1227 | fi | |
1228 | ||
1229 | # create container configuration (will be overwritten by newer lxc-create) | |
1230 | if ! copy_configuration "${rootfs}" "${config}" "${utsname}" | |
1231 | then | |
1232 | echo "Error: Failed write container configuration file" | |
1233 | exit 1 | |
1234 | fi | |
1235 | ||
1236 | if [ -n "${clean}" ]; then | |
1237 | clean || exit 1 | |
1238 | exit 0 | |
1239 | fi | |
1240 | ||
1241 | echo "Successfully created container '${name}'" | |
1242 | ||
1243 | if [ "${root_display_password}" = "yes" ] | |
1244 | then | |
1245 | echo "The temporary password for root is: '$root_password' | |
1246 | ||
1247 | You may want to note that password down before starting the container. | |
1248 | " | |
1249 | fi | |
1250 | ||
1251 | if [ "${root_store_password}" = "yes" ] | |
1252 | then | |
1253 | echo "The temporary root password is stored in: | |
1254 | ||
1255 | '${config}/tmp_root_pass' | |
1256 | " | |
1257 | fi | |
1258 | ||
1259 | if [ "${root_prompt_password}" = "yes" ] | |
1260 | then | |
1261 | echo "Invoking the passwd command in the container to set the root password. | |
1262 | ||
1263 | chroot ${rootfs} passwd | |
1264 | " | |
1265 | chroot "${rootfs}" passwd | |
1266 | else | |
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 " | |
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): | |
1284 | ||
1285 | chroot ${rootfs} passwd | |
1286 | " | |
1287 | fi | |
1288 | fi | |
1289 | fi | |
1290 | ||
1291 | # vim: set ts=4 sw=4 expandtab: |