1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
|
#!/bin/sh
#
# Copyright 2020 Johannes Schauer Marin Rodrigues <josch@debian.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# this script is part of debbisect and usually called by debbisect itself
#
# it accepts eight or ten arguments:
# 1. dependencies
# 2. script name or shell snippet
# 3. mirror URL
# 4. architecture
# 5. suite
# 6. components
# 7. memsize
# 8. disksize
# 9. (optional) second mirror URL
# 10. (optional) package to upgrade
#
# It will create an ephemeral qemu virtual machine using mmdebstrap and
# guestfish using (3.) as mirror, (4.) as architecture, (5.) as suite and
# (6.) as components, install the dependencies given in (1.) and execute the
# script given in (2.).
# Its output is the exit code of the script as well as a file ./pkglist
# containing the output of "dpkg-query -W" inside the chroot.
#
# If not only six but eight arguments are given, then the second mirror URL
# (9.) will be added to the apt sources and the single package (10.) will be
# upgraded to its version from (9.).
set -exu
if [ $# -ne 8 ] && [ $# -ne 10 ]; then
echo "usage: $0 depends script mirror1 architecture suite components memsize disksize [mirror2 toupgrade]"
exit 1
fi
depends=$1
script=$2
mirror1=$3
architecture=$4
suite=$5
components=$6
memsize=$7
disksize=$8
if [ $# -eq 10 ]; then
mirror2=$9
toupgrade=${10}
fi
case $architecture in
alpha) qemuarch=alpha;;
amd64) qemuarch=x86_64;;
arm) qemuarch=arm;;
arm64) qemuarch=aarch64;;
armel) qemuarch=arm;;
armhf) qemuarch=arm;;
hppa) qemuarch=hppa;;
i386) qemuarch=i386;;
m68k) qemuarch=m68k;;
mips) qemuarch=mips;;
mips64) qemuarch=mips64;;
mips64el) qemuarch=mips64el;;
mipsel) qemuarch=mipsel;;
powerpc) qemuarch=ppc;;
ppc64) qemuarch=ppc64;;
ppc64el) qemuarch=ppc64le;;
riscv64) qemuarch=riscv64;;
s390x) qemuarch=s390x;;
sh4) qemuarch=sh4;;
sparc) qemuarch=sparc;;
sparc64) qemuarch=sparc64;;
*) echo "no qemu support for $architecture"; exit 1;;
esac
case $architecture in
i386) linuxarch=686-pae;;
amd64) linuxarch=amd64;;
arm64) linuxarch=arm64;;
armhf) linuxarch=armmp;;
ia64) linuxarch=itanium;;
m68k) linuxarch=m68k;;
armel) linuxarch=marvell;;
hppa) linuxarch=parisc;;
powerpc) linuxarch=powerpc;;
ppc64) linuxarch=powerpc64;;
ppc64el) linuxarch=powerpc64le;;
riscv64) linuxarch=riscv64;;
s390x) linuxarch=s390x;;
sparc64) linuxarch=sparc64;;
*) echo "no kernel image for $architecture"; exit 1;;
esac
TMPDIR=$(mktemp --tmpdir --directory debbisect_qemu.XXXXXXXXXX)
cleantmp() {
for f in customize.sh id_rsa id_rsa.pub qemu.log config; do
rm -f "$TMPDIR/$f"
done
rmdir "$TMPDIR"
}
trap cleantmp EXIT
# the temporary directory must be world readable (for example in unshare mode)
chmod a+xr "$TMPDIR"
ssh-keygen -q -t rsa -f "$TMPDIR/id_rsa" -N ""
cat << SCRIPT > "$TMPDIR/customize.sh"
#!/bin/sh
set -exu
rootfs="\$1"
# setup various files in /etc
echo host > "\$rootfs/etc/hostname"
echo "127.0.0.1 localhost host" > "\$rootfs/etc/hosts"
echo "/dev/vda1 / auto errors=remount-ro 0 1" > "\$rootfs/etc/fstab"
cat /etc/resolv.conf > "\$rootfs/etc/resolv.conf"
# setup users
chroot "\$rootfs" passwd --delete root
chroot "\$rootfs" useradd --home-dir /home/user --create-home user
chroot "\$rootfs" passwd --delete user
# extlinux config to boot from /dev/vda1 with predictable network interface
# naming and a serial console for logging
cat << END > "\$rootfs/extlinux.conf"
default linux
timeout 0
label linux
kernel /vmlinuz
append initrd=/initrd.img root=/dev/vda1 net.ifnames=0 console=ttyS0
END
# network interface config
# we can use eth0 because we boot with net.ifnames=0 for predictable interface
# names
cat << END > "\$rootfs/etc/network/interfaces"
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
END
# copy in the public key
mkdir "\$rootfs/root/.ssh"
cp "$TMPDIR/id_rsa.pub" "\$rootfs/root/.ssh/authorized_keys"
chroot "\$rootfs" chown 0:0 /root/.ssh/authorized_keys
SCRIPT
chmod +x "$TMPDIR/customize.sh"
mmdebstrap --architecture=$architecture --verbose --variant=apt --components="$components" \
--aptopt='Acquire::Check-Valid-Until "false"' \
--include='openssh-server,systemd-sysv,ifupdown,netbase,isc-dhcp-client,udev,policykit-1,linux-image-'"$linuxarch" \
--customize-hook="$TMPDIR/customize.sh" \
"$suite" debian-rootfs.tar "$mirror1"
# use guestfish to prepare the host system
#
# - create a single 4G partition and unpack the rootfs tarball into it
# - unpack the tarball of the container into /
# - put a syslinux MBR into the first 440 bytes of the drive
# - install extlinux and make partition bootable
#
# useful stuff to debug any errors:
# LIBGUESTFS_BACKEND_SETTINGS=force_tcg
# libguestfs-test-tool || true
# export LIBGUESTFS_DEBUG=1 LIBGUESTFS_TRACE=1
guestfish -N "debian-rootfs.img"=disk:$disksize -- \
part-disk /dev/sda mbr : \
mkfs ext4 /dev/sda1 : \
mount /dev/sda1 / : \
tar-in "debian-rootfs.tar" / : \
upload /usr/lib/SYSLINUX/mbr.bin /mbr.bin : \
copy-file-to-device /mbr.bin /dev/sda size:440 : \
rm /mbr.bin : \
extlinux / : \
sync : \
umount / : \
part-set-bootable /dev/sda 1 true : \
shutdown
# start the host system
# prefer using kvm but fall back to tcg if not available
# avoid entropy starvation by feeding the crypt system with random bits from /dev/urandom
# the default memory size of 128 MiB is not enough for Debian, so we go with 1G
# use a virtio network card instead of emulating a real network device
# we don't need any graphics
# this also multiplexes the console and the monitor to stdio
# creates a multiplexed stdio backend connected to the serial port and the qemu
# monitor
# redirect tcp connections on port 10022 localhost to the host system port 22
# redirect all output to a file
# run in the background
timeout --kill-after=60s 60m \
qemu-system-"$qemuarch" \
-M accel=kvm:tcg \
-no-user-config \
-object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 \
-m $memsize \
-net nic,model=virtio \
-nographic \
-serial mon:stdio \
-net user,hostfwd=tcp:127.0.0.1:10022-:22 \
-drive file="debian-rootfs.img",format=raw,if=virtio \
> "$TMPDIR/qemu.log" </dev/null 2>&1 &
# store the pid
QEMUPID=$!
# use a function here, so that we can properly quote the path to qemu.log
showqemulog() {
cat --show-nonprinting "$TMPDIR/qemu.log"
}
# show the log and kill qemu in case the script exits first
trap "showqemulog; cleantmp; kill $QEMUPID" EXIT
# the default ssh command does not store known hosts and even ignores host keys
# it identifies itself with the rsa key generated above
# pseudo terminal allocation is disabled or otherwise, programs executed via
# ssh might wait for input on stdin of the ssh process
cat << END > "$TMPDIR/config"
Host qemu
Hostname 127.0.0.1
User root
Port 10022
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
IdentityFile $TMPDIR/id_rsa
RequestTTY no
END
TIMESTAMP=$(sleepenh 0 || [ $? -eq 1 ])
TIMEOUT=5
NUM_TRIES=40
i=0
while true; do
rv=0
ssh -F "$TMPDIR/config" -o ConnectTimeout=$TIMEOUT qemu echo success || rv=1
[ $rv -eq 0 ] && break
# if the command before took less than $TIMEOUT seconds, wait the remaining time
TIMESTAMP=$(sleepenh $TIMESTAMP $TIMEOUT || [ $? -eq 1 ]);
i=$((i+1))
if [ $i -ge $NUM_TRIES ]; then
break
fi
done
if [ $i -eq $NUM_TRIES ]; then
echo "timeout reached: unable to connect to qemu via ssh"
exit 1
fi
# if any url in sources.list points to 127.0.0.1 then we have to replace them
# by the host IP as seen by the qemu guest
cat << SCRIPT | ssh -F "$TMPDIR/config" qemu sh
set -eu
if [ -e /etc/apt/sources.list ]; then
sed -i 's/http:\/\/127.0.0.1:/http:\/\/10.0.2.2:/' /etc/apt/sources.list
fi
find /etc/apt/sources.list.d -type f -name '*.list' -print0 \
| xargs --null --no-run-if-empty sed -i 's/http:\/\/127.0.0.1:/http:\/\/10.0.2.2:/'
SCRIPT
# we install dependencies now and not with mmdebstrap --include in case some
# dependencies require a full system present
ssh -F "$TMPDIR/config" qemu apt-get update
ssh -F "$TMPDIR/config" qemu env DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get --yes install --no-install-recommends $(echo $depends | tr ',' ' ')
# in its ten-argument form, a single package has to be upgraded to its
# version from the first bad timestamp
if [ $# -eq 10 ]; then
# replace content of sources.list with first bad timestamp
mirror2=$(echo "$mirror2" | sed 's/http:\/\/127.0.0.1:/http:\/\/10.0.2.2:/')
echo "deb $mirror2 $suite $(echo "$components" | tr ',' ' ')" | ssh -F "$TMPDIR/config" qemu "cat > /etc/apt/sources.list"
ssh -F "$TMPDIR/config" qemu apt-get update
# upgrade a single package (and whatever else apt deems necessary)
before=$(ssh -F "$TMPDIR/config" qemu dpkg-query -W)
ssh -F "$TMPDIR/config" qemu env DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get --yes install --no-install-recommends "$toupgrade"
after=$(ssh -F "$TMPDIR/config" qemu dpkg-query -W)
# make sure that something was upgraded
if [ "$before" = "$after" ]; then
echo "nothing got upgraded -- this should never happen" >&2
exit 1
fi
ssh -F "$TMPDIR/config" qemu dpkg-query -W > "./debbisect.$DEBIAN_BISECT_TIMESTAMP.$toupgrade.pkglist"
else
ssh -F "$TMPDIR/config" qemu dpkg-query -W > "./debbisect.$DEBIAN_BISECT_TIMESTAMP.pkglist"
fi
ssh -F "$TMPDIR/config" qemu dpkg -l
# explicitly export all necessary variables
# because we use set -u this also makes sure that this script has these
# variables set in the first place
export DEBIAN_BISECT_EPOCH=$DEBIAN_BISECT_EPOCH
export DEBIAN_BISECT_TIMESTAMP=$DEBIAN_BISECT_TIMESTAMP
if [ -z ${DEBIAN_BISECT_MIRROR+x} ]; then
# DEBIAN_BISECT_MIRROR was unset (caching is disabled)
true
else
# replace the localhost IP by the IP of the host as seen by qemu
DEBIAN_BISECT_MIRROR=$(echo "$DEBIAN_BISECT_MIRROR" | sed 's/http:\/\/127.0.0.1:/http:\/\/10.0.2.2:/')
export DEBIAN_BISECT_MIRROR=$DEBIAN_BISECT_MIRROR
fi
# either execute $script as a script from $PATH or as a shell snippet
ret=0
if [ -x "$script" ] || echo "$script" | grep --invert-match --silent --perl-regexp '[^\w@\%+=:,.\/-]'; then
"$script" "$TMPDIR/config" || ret=$?
else
sh -c "$script" exec "$TMPDIR/config" || ret=$?
fi
# since we installed systemd-sysv, systemctl is available
ssh -F "$TMPDIR/config" qemu systemctl poweroff
wait $QEMUPID
trap - EXIT
showqemulog
cleantmp
if [ "$ret" -eq 0 ]; then
exit 0
else
exit 1
fi
|