#!/bin/bash # create a VM image using debootstrap # # Copyright 2013, 2014, 2015 Peter Palfrader # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You probably already have access to a copy of the GNU General Public # License or you can find one on the Internet; if not, write to the # copyright holder or to the Free Software Foundation, Inc., 59 Temple # Place, Suite 330, Boston, MA 02111-1307 USA. set -e set -u SUITE=stretch hostlistfile=/root/VMs/host-list MIRROR=http://ftp.at.debian.org/debian KEYRING=/usr/share/keyrings/debian-archive-keyring.gpg prefixlen=26 gateway=213.235.231.193 ip6prefix=2001:858:10f:100:: ip6gateway=fe80:: basedir=/srv/vmstore check_installed() { local p for p in "$@"; do if ! dpkg -l "$p" 2>/dev/null | grep -q '^ii'; then echo >&2 "Error: package $p not installed:" echo >&2 " apt-get install $*" exit 1 fi done } get_sshkey_fprs() { local f for f in etc/ssh/ssh_host*_key.pub; do echo -n " " ssh-keygen -l -f "$f" done echo for f in etc/ssh/ssh_host*_key.pub; do echo -n " " ssh-keygen -r '' -f "$f" done } do_cleanup() { #echo "Proposed cleanup:" local cnt cnt=$((${#cleanup[*]}-1)) #for i in $(seq ${cnt} -1 0); do # echo " ${cleanup[$i]}" #done #echo "Press enter to commence cleanup." #read for i in $(seq ${cnt} -1 0); do echo "* ${cleanup[$i]}" eval "${cleanup[$i]}" || true done echo "done." } if [ -e /etc/debian_version ]; then check_installed debootstrap debian-archive-keyring kpartx else test -e `which debootstrap >/dev/null 2>&1` || echo "W: could not find debootstrap binary" test -e `which kpartx >/dev/null 2>&1` || echo "W: could not find kpartx binary" test -e $KEYRING || echo "W: could not find keyring in $KEYRING" fi declare -a cleanup cleanup+=(":") trap do_cleanup EXIT echo -n "New VM's FQDN: " if [ -n "${1:-}" ]; then echo "$1"; fqdn="$1"; shift; else read fqdn; fi echo guest="${fqdn%%.*}" domainname="${fqdn#*.}" echo -n "Disk size: [10g]" if [ -n "${1:-}" ]; then echo "$1"; disksize="$1"; shift; else read disksize; fi disksize=${disksize:-10g} echo -n "Swap size: [4g]" if [ -n "${1:-}" ]; then echo "$1"; swapsize="$1"; shift; else read swapsize; fi swapsize=${swapsize:-4g} echo -n "Lvm size: [20g]" if [ -n "${1:-}" ]; then echo "$1"; lvmsize="$1"; shift; else read lvmsize; fi lvmsize=${lvmsize:-20g} if [ "$lvmsize" = "0" ]; then lvmsize=""; fi echo -n "ipaddr: "; if [ -n "${1:-}" ]; then echo "$1"; ipaddr="$1"; shift; else read ipaddr; fi echo -n "unique hostno (2-254): "; if [ -n "${1:-}" ]; then echo "$1"; hostno="$1"; shift; else read hostno; fi if awk -v v="$hostno" '$1 == v {print}' "$hostlistfile" | grep .; then echo >&2 "Host number $hostno already used in $hostlistfile." exit 1 fi if awk -v v="$ipaddr" '$2 == v {print}' "$hostlistfile" | grep .; then echo >&2 "IP address $ipaddr already used in $hostlistfile." exit 1 fi if awk -v v="$fqdn" '$3 == v {print}' "$hostlistfile" | grep .; then echo >&2 "FQDN $fqdn already used in $hostlistfile." exit 1 fi diskfileprefix="$basedir/$fqdn/" if [ -e "$diskfileprefix" ]; then echo >&2 "Error: Disk directory $diskfileprefix already exists." exit 1 fi target="/mnt/target-$guest" if [ -e "$target" ]; then echo >&2 "Error: Directory $target already exists." exit 1 fi diskfileroot="${diskfileprefix}$fqdn-root" diskfileswap="${diskfileprefix}$fqdn-swap" diskfilelvm="${diskfileprefix}$fqdn-lvm" mkdir "$diskfileprefix" (umask 077 && qemu-img create -f qcow2 "$diskfileroot" "$disksize") (umask 077 && qemu-img create -f raw "$diskfileswap" "$swapsize") if [ "$lvmsize" != "" ] ; then (umask 077 && qemu-img create -f qcow2 "$diskfilelvm" "$lvmsize") fi modprobe nbd max_part=63 rootdev=/dev/nbd0 lvmdev=/dev/nbd1 qemu-nbd -c "$rootdev" "$diskfileroot" cleanup+=("qemu-nbd -d '$rootdev'") if [ "$lvmsize" != "" ] ; then qemu-nbd -c "$lvmdev" "$diskfilelvm" cleanup+=("qemu-nbd -d '$lvmdev'") fi if [ "$(head -c 65536 "$rootdev" | sha1sum | awk '{print $1}')" != "1adc95bebe9eea8c112d40cd04ab7a8d75c4f961" ]; then echo -n "Warning: Disk appears to be not be empty. Continue anyway? [y/N] " read ans [ "$ans" = "y" ] || exit 0 fi ip6addr="$ip6prefix$hostno:1" echo '2048,,L,*' | sfdisk -u S "$rootdev" kpartx -v -p "-p" -a "$rootdev" -s cleanup+=("kpartx -d -p '-p' -v '$rootdev'") part1="/dev/mapper/$(basename $rootdev)-p1" mkfs.ext4 "$part1" if vgs "vg_$guest" > /dev/null 2>&1 ; then echo >&2 "Error: Volume group vg_$guest already exists." exit 1 fi if [ "$lvmsize" != "" ] ; then # pvcreate "$lvmdev" || read dummy # we want to remove the vg from our local metadata config, but we do not # affect the devices, so add it to the beginning of the list so it gets run # when the devices are all gone. cleanup=("vgremove 'vg_$guest'" "${cleanup[@]}") vgcreate "vg_$guest" "$lvmdev" fi mkdir "$target" cleanup+=("rmdir '$target'") mount "$part1" "$target" cleanup+=("umount '$target'") cd "$target" cleanup+=("cd /") debootstrap --variant=minbase --keyring="$KEYRING" "$SUITE" . "$MIRROR" ### Set up swap and fstab mkswap "$diskfileswap" uuidroot=$(blkid -s UUID -o value ${part1}) && uuidswap=$(blkid -s UUID -o value $diskfileswap) && cat > etc/fstab << EOF UUID=$uuidroot / ext4 errors=remount-ro 0 1 UUID=$uuidswap none swap sw 0 0 tmpfs /tmp tmpfs defaults,size=512m 0 0 EOF ### Set up basic networking stuff echo "$guest" > etc/hostname cat > etc/hosts << EOF 127.0.0.1 localhost $ipaddr $fqdn $guest # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts EOF rm -fv etc/udev/rules.d/70-persistent-* mkdir -p etc/udev/rules.d/ touch etc/udev/rules.d/75-persistent-net-generator.rules mkdir -p etc/network cat > etc/network/interfaces << EOF auto lo iface lo inet loopback allow-hotplug eth0 iface eth0 inet static pre-up echo 1 > /proc/sys/net/ipv6/conf/\$IFACE/disable_ipv6 address $ipaddr/$prefixlen gateway $gateway allow-hotplug eth1 iface eth1 inet static address 172.22.130.$hostno/23 iface eth1 inet6 static address $ip6addr/64 gateway $ip6gateway accept_ra 0 EOF cat > etc/resolv.conf << EOF nameserver 172.22.130.1 search $domainname EOF ### A couple packages mv etc/apt/sources.list etc/apt/sources.list.d/debian.list echo "deb http://security.debian.org/ ${SUITE}/updates main" > etc/apt/sources.list.d/security.list echo "deb $MIRROR ${SUITE}-updates main" > etc/apt/sources.list.d/updates.list chroot . apt-get update echo "Apt::Install-Recommends 0;" > etc/apt/apt.conf.d/local-recommends chroot . apt-get install -y locales-all net-tools iproute ifupdown dialog vim netbase udev psmisc usbutils pciutils chroot . apt-get install -y iputils-ping telnet bind9-host lvm2 sed -i -e 's/issue_discards = 0/issue_discards = 1/' etc/lvm/lvm.conf # init stuff chroot . apt-get install -y systemd systemd-sysv initscripts ### Set up kernel and bootloader chroot . apt-get install -y linux-image-amd64 DEBIAN_FRONTEND=noninteractive chroot . apt-get install -y grub2 ! [ -e dev/sda ] ! [ -e dev/sda1 ] cp -av `readlink -f "$rootdev"` dev/new-root cp -av `readlink -f "$part1"` dev/new-root1 chroot . grub-install --modules=part_msdos /dev/new-root rm -v dev/new-root* cp -av `readlink -f "$rootdev"` dev/sda cp -av `readlink -f "$part1"` dev/sda1 rm -f boot/grub/device.map sed -i -e 's/^GRUB_CMDLINE_LINUX=""$/GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8 net.ifnames=0"/' etc/default/grub cat >> etc/default/grub < etc/apt/apt.conf.d/20auto-upgrades << EOF APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; EOF ### more packages chroot . apt-get install -y libpam-systemd dbus cron logrotate rsyslog qemu-guest-agent acpi-support-base lldpd ## trim mkdir -p etc/cron.daily tee > etc/cron.daily/local-trim-ext4 << 'EOF' #!/bin/sh # by weasel for fs in `awk '$9 == "ext4" && $4 == "/" {print $5}' /proc/self/mountinfo`; do fstrim "$fs" done EOF chmod +x etc/cron.daily/local-trim-ext4 ## set up ntp cat >> etc/systemd/timesyncd.conf << EOF Servers=172.22.130.1 EOF if ! [ -e etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service ] ; then ln -s /lib/systemd/system/systemd-timesyncd.service etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service fi ### first boot cat > first-boot.sh << EOF #!/bin/bash set -e set -x sleep 10 #apt-get install -y XXX update-initramfs -u sed -i -e '/first-boot.sh/d' etc/rc.local /etc/cron.daily/local-trim-ext4 rm /first-boot.sh (sleep 10 ; /sbin/reboot) & disown %1 EOF chmod +x first-boot.sh sed -i -e '$ i [ -x /first-boot.sh ] && /first-boot.sh' etc/rc.local ### clean up chroot . apt-get clean # prepare virsh file ( : #) cat << EOF $fqdn $(uuidgen) 1024 2 hvm SandyBridge destroy restart restart /usr/bin/kvm EOF [ "$lvmsize" = "" ] || cat << EOF EOF cat << EOF /dev/random
EOF ) > "$diskfileprefix/$guest.xml" # and done trap - EXIT do_cleanup echo "$guest's root password is $rootpw" echo "SSH host key fingerprints are:" echo "$sshkeys" echo "IP addresses:" echo " $ipaddr" echo " $ip6addr" echo echo "Maybe run" echo " echo '$hostno' '$ipaddr' '$fqdn' >> '$hostlistfile'" echo " virsh define '$diskfileprefix/$guest.xml' && rm '$diskfileprefix/$guest.xml'" echo " virsh autostart '$fqdn'" echo echo ' for i in `/usr/local/sbin/vm_du_ suggest`; do ln -vsf /usr/local/sbin/vm_du_ /etc/munin/plugins/vm_du_$i; done' echo ' service munin-node restart; sleep 3; service munin-async restart' echo echo " virsh start '$fqdn'" echo " virsh console '$fqdn'"