blob: 438d97ef10f2531b45d31e3cd124286ef2e2b39f [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"
yatinkarel20d0da02025-05-12 15:54:50 +053070 if [ -f /etc/apt/sources.list ]; then
71 mirror=$(grep -oP 'https?://\K[^/ ]+' /etc/apt/sources.list|head -1)
yatinkarelf345ae02025-07-10 16:31:15 +053072 if [ -n "${mirror}" ]; then
73 if sudo test -f ${mount_dir}/etc/apt/sources.list.d/ubuntu.sources; then
74 sudo sed -Ei "s|(http[s]?://)([^/]+)|\1${mirror}|g" ${mount_dir}/etc/apt/sources.list.d/ubuntu.sources
75 sudo sed -i "/URIs:/a Trusted: yes" ${mount_dir}/etc/apt/sources.list.d/ubuntu.sources
76 elif sudo test -f ${mount_dir}/etc/apt/sources.list; then
77 source <(sudo cat ${mount_dir}/etc/os-release)
78 sudo tee ${mount_dir}/etc/apt/sources.list <<EOF
79 deb [ trusted=yes ] https://${mirror}/ubuntu ${UBUNTU_CODENAME} main universe
80 deb [ trusted=yes ] https://${mirror}/ubuntu ${UBUNTU_CODENAME}-updates main universe
81 deb [ trusted=yes ] https://${mirror}/ubuntu ${UBUNTU_CODENAME}-backports main universe
82 deb [ trusted=yes ] https://${mirror}/ubuntu ${UBUNTU_CODENAME}-security main universe
yatinkarel20d0da02025-05-12 15:54:50 +053083EOF
yatinkarelf345ae02025-07-10 16:31:15 +053084 fi
yatinkarel20d0da02025-05-12 15:54:50 +053085 fi
86 fi
Federico Ressi08c74e92018-06-12 14:19:21 +020087
88 # Mount to keep temporary files out of guest image
89 mkdir -p "${temp_dir}/apt" "${temp_dir}/cache" "${temp_dir}/tmp"
90 bind_dir "${temp_dir}/cache" "${mount_dir}/var/cache"
91 bind_dir "${temp_dir}/tmp" "${mount_dir}/tmp"
92 bind_dir "${temp_dir}/tmp" "${mount_dir}/var/tmp"
93 bind_dir "${temp_dir}/apt" "${mount_dir}/var/lib/apt"
94
Federico Ressi71bda862018-05-28 11:38:56 +020095 # Temporarly replace /etc/resolv.conf symlink to use the same DNS as this
96 # host
97 local resolv_file=${mount_dir}/etc/resolv.conf
98 sudo mv -f "${resolv_file}" "${resolv_file}.orig"
99 sudo cp /etc/resolv.conf "${resolv_file}"
100 add_cleanup sudo mv -f "${resolv_file}.orig" "${resolv_file}"
Federico Ressi08c74e92018-06-12 14:19:21 +0200101
102 # Makesure /etc/fstab exists and it is readable because it is required by
103 # /sbin/dhclient-script
104 sudo touch /etc/fstab
105 sudo chmod 644 /etc/fstab
106
107 # Copy this script to mount dir
108 local script_name=$(basename "$0")
109 local script_file=${mount_dir}/${script_name}
110 sudo cp "$0" "${script_file}"
111 sudo chmod 500 "${script_file}"
112 add_cleanup sudo rm -f "'${script_file}'"
113
114 # Execute customize_image inside chroot environment
115 local command_line=( ${CHROOT_COMMAND:-customize_image} )
116 local entry_point=${command_line[0]}
117 unset command_line[0]
118 sudo -E "ENTRY_POINT=${entry_point}" \
119 chroot "${mount_dir}" "/${script_name}" "${command_line[@]:-}"
120}
121
122# Mounts guest image to $1 directory writing pid to $1 pid file
123# Then registers umount of such directory for final cleanup
124function mount_image {
125 local mount_dir=$1
126 local pid_file=$2
127
128 # export libguest settings
129 export LIBGUESTFS_BACKEND=${LIBGUESTFS_BACKEND:-direct}
130 export LIBGUESTFS_BACKEND_SETTINGS=${LIBGUESTFS_BACKEND_SETTINGS:-force_tcg}
131
132 # Mount guest image
133 sudo -E guestmount -i \
134 --add "${image_file}" \
135 --pid-file "${pid_file}" \
136 "${mount_dir}"
137
138 add_cleanup \
139 'ENTRY_POINT=umount_image' \
140 "'$0'" "'${mount_dir}'" "'${pid_file}'"
141}
142
143# Unmounts guest image directory
144function umount_image {
145 local mount_dir=$1
146 local pid_file=$2
147 local timeout=10
148
149 # Take PID just before unmounting
150 local pid=$(cat ${pid_file} || true)
151 sudo -E guestunmount "${mount_dir}"
152
153 if [ "${pid:-}" != "" ]; then
154 # Make sure guestmount process is not running before using image
155 # file again
156 local count=${timeout}
157 while sudo kill -0 "${pid}" 2> /dev/null && (( count-- > 0 )); do
158 sleep 1
159 done
160 if [ ${count} == 0 ]; then
161 # It is not safe to use image file at this point
162 echo "Wait for guestmount to exit failed after ${timeout} seconds"
163 fi
164 fi
165}
166
167# Creates a temporary file or directory and register removal for final cleanup
168function make_temp {
169 local temporary=$(mktemp "$@")
170 add_cleanup sudo rm -fR "'${temporary}'"
171 echo "${temporary}"
172}
173
174# Bind directory $1 to directory $2 and register umount for final cleanup
175function bind_dir {
176 local source_dir=$1
177 local target_dir=$2
178 sudo mount --bind "${source_dir}" "${target_dir}"
179 add_cleanup sudo umount "'${target_dir}'"
180}
181
182# Registers a command line to be executed for final cleanup
183function add_cleanup {
184 CLEANUP_FILE=${CLEANUP_FILE:-$(mktemp)}
185
186 echo -e "$*" >> ${CLEANUP_FILE}
187}
188
189# Execute command lines for final cleanup in reversed order
190function cleanup {
191 error=$?
192
193 local cleanup_file=${CLEANUP_FILE:-}
194 if [ -r "${cleanup_file}" ]; then
195 tac "${cleanup_file}" | bash +e -x
196 CLEANUP_FILE=
197 rm -fR "${cleanup_file}"
198 fi
199
200 exit ${error}
201}
202
203main "$@"