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