blob: 9c3fd0784d43d40999910912823ef60298f01037 [file] [log] [blame]
Federico Ressi08c74e92018-06-12 14:19:21 +02001#!/bin/bash
2
3# IMPLEMENTATION NOTE: It was not possible to implement this script using
4# virt-customize because of below ubuntu bugs:
5# - https://bugs.launchpad.net/ubuntu/+source/libguestfs/+bug/1632405
6# - https://bugs.launchpad.net/ubuntu/+source/isc-dhcp/+bug/1650740
7#
8# It has therefore been adopted a more low level strategy performing below
9# steps:
10# - mount guest image to a temporary folder
11# - set up an environment suitable for executing chroot
12# - execute customize_image function inside chroot environment
13# - cleanup chroot environment
14
15# Array of packages to be installed of guest image
16INSTALL_GUEST_PACKAGES=(
17 socat # used to replace nc for testing advanced network features like
18 # multicast
19)
20
21# Function to be executed once after chroot on guest image
22# Add more customization steps here
23function customize_image {
24 # dhclient-script requires to read /etc/fstab for setting up network
25 touch /etc/fstab
26 chmod ugo+r /etc/fstab
27
28 # Ubuntu guest image _apt user could require access to below folders
29 local apt_user_folders=( /var/lib/apt/lists/partial )
30 mkdir -p "${apt_user_folders[@]}"
31 chown _apt.root -fR "${apt_user_folders[@]}"
32
33 # Install desired packages to Ubuntu guest image
34 apt-get update -y
35 apt-get install -y "${INSTALL_GUEST_PACKAGES[@]}"
36}
37
38function main {
39 set -eux
40 trap cleanup EXIT
41 "${ENTRY_POINT:-chroot_image}" "$@"
42}
43
44# Chroot to guest image then executes customize_image function inside it
45function chroot_image {
46 local image_file=$1
47 local temp_dir=${TEMP_DIR:-$(make_temp -d)}
48
49 # Mount guest image into a temporary directory
50 local mount_dir=${temp_dir}/mount
51 mkdir -p "${mount_dir}"
52 mount_image "${mount_dir}" "${temp_dir}/pid"
53
54 # Mount system directories
55 bind_dir "/dev" "${mount_dir}/dev"
56 bind_dir "/dev/pts" "${mount_dir}/dev/pts"
57 bind_dir "/proc" "${mount_dir}/proc"
58 bind_dir "/sys" "${mount_dir}/sys"
59
60 # Mount to keep temporary files out of guest image
61 mkdir -p "${temp_dir}/apt" "${temp_dir}/cache" "${temp_dir}/tmp"
62 bind_dir "${temp_dir}/cache" "${mount_dir}/var/cache"
63 bind_dir "${temp_dir}/tmp" "${mount_dir}/tmp"
64 bind_dir "${temp_dir}/tmp" "${mount_dir}/var/tmp"
65 bind_dir "${temp_dir}/apt" "${mount_dir}/var/lib/apt"
66
67 # Replace /etc/resolv.conf symlink to use the same DNS as this host
68 sudo rm -f "${mount_dir}/etc/resolv.conf"
69 sudo cp /etc/resolv.conf "${mount_dir}/etc/resolv.conf"
70
71 # Makesure /etc/fstab exists and it is readable because it is required by
72 # /sbin/dhclient-script
73 sudo touch /etc/fstab
74 sudo chmod 644 /etc/fstab
75
76 # Copy this script to mount dir
77 local script_name=$(basename "$0")
78 local script_file=${mount_dir}/${script_name}
79 sudo cp "$0" "${script_file}"
80 sudo chmod 500 "${script_file}"
81 add_cleanup sudo rm -f "'${script_file}'"
82
83 # Execute customize_image inside chroot environment
84 local command_line=( ${CHROOT_COMMAND:-customize_image} )
85 local entry_point=${command_line[0]}
86 unset command_line[0]
87 sudo -E "ENTRY_POINT=${entry_point}" \
88 chroot "${mount_dir}" "/${script_name}" "${command_line[@]:-}"
89}
90
91# Mounts guest image to $1 directory writing pid to $1 pid file
92# Then registers umount of such directory for final cleanup
93function mount_image {
94 local mount_dir=$1
95 local pid_file=$2
96
97 # export libguest settings
98 export LIBGUESTFS_BACKEND=${LIBGUESTFS_BACKEND:-direct}
99 export LIBGUESTFS_BACKEND_SETTINGS=${LIBGUESTFS_BACKEND_SETTINGS:-force_tcg}
100
101 # Mount guest image
102 sudo -E guestmount -i \
103 --add "${image_file}" \
104 --pid-file "${pid_file}" \
105 "${mount_dir}"
106
107 add_cleanup \
108 'ENTRY_POINT=umount_image' \
109 "'$0'" "'${mount_dir}'" "'${pid_file}'"
110}
111
112# Unmounts guest image directory
113function umount_image {
114 local mount_dir=$1
115 local pid_file=$2
116 local timeout=10
117
118 # Take PID just before unmounting
119 local pid=$(cat ${pid_file} || true)
120 sudo -E guestunmount "${mount_dir}"
121
122 if [ "${pid:-}" != "" ]; then
123 # Make sure guestmount process is not running before using image
124 # file again
125 local count=${timeout}
126 while sudo kill -0 "${pid}" 2> /dev/null && (( count-- > 0 )); do
127 sleep 1
128 done
129 if [ ${count} == 0 ]; then
130 # It is not safe to use image file at this point
131 echo "Wait for guestmount to exit failed after ${timeout} seconds"
132 fi
133 fi
134}
135
136# Creates a temporary file or directory and register removal for final cleanup
137function make_temp {
138 local temporary=$(mktemp "$@")
139 add_cleanup sudo rm -fR "'${temporary}'"
140 echo "${temporary}"
141}
142
143# Bind directory $1 to directory $2 and register umount for final cleanup
144function bind_dir {
145 local source_dir=$1
146 local target_dir=$2
147 sudo mount --bind "${source_dir}" "${target_dir}"
148 add_cleanup sudo umount "'${target_dir}'"
149}
150
151# Registers a command line to be executed for final cleanup
152function add_cleanup {
153 CLEANUP_FILE=${CLEANUP_FILE:-$(mktemp)}
154
155 echo -e "$*" >> ${CLEANUP_FILE}
156}
157
158# Execute command lines for final cleanup in reversed order
159function cleanup {
160 error=$?
161
162 local cleanup_file=${CLEANUP_FILE:-}
163 if [ -r "${cleanup_file}" ]; then
164 tac "${cleanup_file}" | bash +e -x
165 CLEANUP_FILE=
166 rm -fR "${cleanup_file}"
167 fi
168
169 exit ${error}
170}
171
172main "$@"