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