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