blob: 3697265b68597361dc837d8fae189f785b9cccd7 [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
Federico Ressi71bda862018-05-28 11:38:56 +020034 (
35 DEBIAN_FRONTEND=noninteractive
36 apt-get update -y
37 apt-get install -y "${INSTALL_GUEST_PACKAGES[@]}"
38 )
Federico Ressi08c74e92018-06-12 14:19:21 +020039}
40
41function main {
42 set -eux
43 trap cleanup EXIT
44 "${ENTRY_POINT:-chroot_image}" "$@"
45}
46
47# Chroot to guest image then executes customize_image function inside it
48function chroot_image {
49 local image_file=$1
50 local temp_dir=${TEMP_DIR:-$(make_temp -d)}
51
52 # Mount guest image into a temporary directory
53 local mount_dir=${temp_dir}/mount
54 mkdir -p "${mount_dir}"
55 mount_image "${mount_dir}" "${temp_dir}/pid"
56
57 # Mount system directories
58 bind_dir "/dev" "${mount_dir}/dev"
59 bind_dir "/dev/pts" "${mount_dir}/dev/pts"
60 bind_dir "/proc" "${mount_dir}/proc"
61 bind_dir "/sys" "${mount_dir}/sys"
62
63 # Mount to keep temporary files out of guest image
64 mkdir -p "${temp_dir}/apt" "${temp_dir}/cache" "${temp_dir}/tmp"
65 bind_dir "${temp_dir}/cache" "${mount_dir}/var/cache"
66 bind_dir "${temp_dir}/tmp" "${mount_dir}/tmp"
67 bind_dir "${temp_dir}/tmp" "${mount_dir}/var/tmp"
68 bind_dir "${temp_dir}/apt" "${mount_dir}/var/lib/apt"
69
Federico Ressi71bda862018-05-28 11:38:56 +020070 # Temporarly replace /etc/resolv.conf symlink to use the same DNS as this
71 # host
72 local resolv_file=${mount_dir}/etc/resolv.conf
73 sudo mv -f "${resolv_file}" "${resolv_file}.orig"
74 sudo cp /etc/resolv.conf "${resolv_file}"
75 add_cleanup sudo mv -f "${resolv_file}.orig" "${resolv_file}"
Federico Ressi08c74e92018-06-12 14:19:21 +020076
77 # Makesure /etc/fstab exists and it is readable because it is required by
78 # /sbin/dhclient-script
79 sudo touch /etc/fstab
80 sudo chmod 644 /etc/fstab
81
82 # Copy this script to mount dir
83 local script_name=$(basename "$0")
84 local script_file=${mount_dir}/${script_name}
85 sudo cp "$0" "${script_file}"
86 sudo chmod 500 "${script_file}"
87 add_cleanup sudo rm -f "'${script_file}'"
88
89 # Execute customize_image inside chroot environment
90 local command_line=( ${CHROOT_COMMAND:-customize_image} )
91 local entry_point=${command_line[0]}
92 unset command_line[0]
93 sudo -E "ENTRY_POINT=${entry_point}" \
94 chroot "${mount_dir}" "/${script_name}" "${command_line[@]:-}"
95}
96
97# Mounts guest image to $1 directory writing pid to $1 pid file
98# Then registers umount of such directory for final cleanup
99function mount_image {
100 local mount_dir=$1
101 local pid_file=$2
102
103 # export libguest settings
104 export LIBGUESTFS_BACKEND=${LIBGUESTFS_BACKEND:-direct}
105 export LIBGUESTFS_BACKEND_SETTINGS=${LIBGUESTFS_BACKEND_SETTINGS:-force_tcg}
106
107 # Mount guest image
108 sudo -E guestmount -i \
109 --add "${image_file}" \
110 --pid-file "${pid_file}" \
111 "${mount_dir}"
112
113 add_cleanup \
114 'ENTRY_POINT=umount_image' \
115 "'$0'" "'${mount_dir}'" "'${pid_file}'"
116}
117
118# Unmounts guest image directory
119function umount_image {
120 local mount_dir=$1
121 local pid_file=$2
122 local timeout=10
123
124 # Take PID just before unmounting
125 local pid=$(cat ${pid_file} || true)
126 sudo -E guestunmount "${mount_dir}"
127
128 if [ "${pid:-}" != "" ]; then
129 # Make sure guestmount process is not running before using image
130 # file again
131 local count=${timeout}
132 while sudo kill -0 "${pid}" 2> /dev/null && (( count-- > 0 )); do
133 sleep 1
134 done
135 if [ ${count} == 0 ]; then
136 # It is not safe to use image file at this point
137 echo "Wait for guestmount to exit failed after ${timeout} seconds"
138 fi
139 fi
140}
141
142# Creates a temporary file or directory and register removal for final cleanup
143function make_temp {
144 local temporary=$(mktemp "$@")
145 add_cleanup sudo rm -fR "'${temporary}'"
146 echo "${temporary}"
147}
148
149# Bind directory $1 to directory $2 and register umount for final cleanup
150function bind_dir {
151 local source_dir=$1
152 local target_dir=$2
153 sudo mount --bind "${source_dir}" "${target_dir}"
154 add_cleanup sudo umount "'${target_dir}'"
155}
156
157# Registers a command line to be executed for final cleanup
158function add_cleanup {
159 CLEANUP_FILE=${CLEANUP_FILE:-$(mktemp)}
160
161 echo -e "$*" >> ${CLEANUP_FILE}
162}
163
164# Execute command lines for final cleanup in reversed order
165function cleanup {
166 error=$?
167
168 local cleanup_file=${CLEANUP_FILE:-}
169 if [ -r "${cleanup_file}" ]; then
170 tac "${cleanup_file}" | bash +e -x
171 CLEANUP_FILE=
172 rm -fR "${cleanup_file}"
173 fi
174
175 exit ${error}
176}
177
178main "$@"