#!/usr/bin/env bash # # kvmx-create virtual machine installer # # Copyright (C) 2017 Silvio Rhatto - rhatto at riseup.net # # 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 3 of the License, # or 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 should have received a copy of the GNU General Public License # along with this program. If not, see . # # Parameters BASENAME="`basename $0`" DIRNAME="`dirname $0`" # Load configuration function kvmx_config_load { if [ ! -z "$1" ] && [ -e "$1" ]; then source $1 fi } # Read a parameter from user function kvmx_user_input { local input local param="$1" local default="$2" shift 2 if echo $param | grep -q 'passwd'; then read -s -rep "$* (defaults to $default): " input else read -rep "$* (defaults to $default): " input fi if [ -z "$input" ]; then export $param="$default" else export $param="$input" fi } # Get a configuration parameter if not previously defined by a sourced file function kvmx_user_config { local param="$1" local default="$2" shift 2 if [ -z "`eval echo '$'$param`" ]; then kvmx_user_input $param $default $* fi } # Install a package function kvmx_install_package { if [ -z "$1" ]; then return fi dpkg -s $1 &> /dev/null if [ "$?" == "1" ]; then echo "Installing package $1..." DEBIAN_FRONTEND=noninteractive $SUDO apt-get install $1 -y || exit 1 fi } # Abort on error function kvmx_exit_on_error { if [ "$?" != "0" ]; then echo "Error: $*" exit 1 fi } # Run a command using sudo and abort on error function kvmx_sudo_run { if [ "`whoami`" != 'root' ]; then SUDO="sudo" fi $SUDO $* kvmx_exit_on_error $* } # Make sure there is provision config. function kvmx_config { kvmx_user_config image /var/cache/qemu/debian/box.img "Destination image (ending in .img)" kvmx_user_config size 3G "Image size" kvmx_user_config format qcow2 "Image format: raw or qcow2" kvmx_user_config method custom "Bootstrap method: custom or vmdeboostrap" kvmx_user_config hostname machine "Hostname" kvmx_user_config domain example.org "Domain" kvmx_user_config arch amd64 "System arch" kvmx_user_config version stretch "Distro version" kvmx_user_config mirror http://http.debian.net/debian/ "Debian mirror" kvmx_user_config ssh_support y "Administration using passwordless SSH (y/n)" kvmx_user_config ssh_custom y "Setup a custom SSH keypair (y/n)" kvmx_user_config user user "Initial user name" kvmx_user_config password $RANDOM "Initial user password" } # Load config file kvmx_config_load $1 # Get config parameters kvmx_config # Check if [ -e "$image" ]; then echo "error: $image already exists." exit 1 fi # Ensure base folder exists kvmx_sudo_run mkdir -p `dirname $image` # # vmdebootstrap version # function kvmx_create_vmdebootstrap { # Check for requirements for req in vmdebootstrap mbr; do kvmx_install_package $req done # Image format if [ "$format" == "qcow2" ]; then format="--convert-qcow2" else formt="" fi # Run kvmx_sudo_run vmdebootstrap --verbose --image=$image --size=$size --distribution=$version \ --mirror=$mirror --arch=$arch --hostname=$hostname.$domain \ --grub $format # Fix permissions kvmx_sudo_run chown -R `whoami`. `dirname $image` # Cleanup kvmx_sudo_run rm debootstrap.log kvmx_sudo_run rm ${image}.raw } # # Custom version # function kvmx_create_custom { WORK="`mktemp -d`" # Check for requirements. for req in debootstrap grub-pc parted; do kvmx_install_package $req done echo "Creating image..." #kvmx_sudo_run dd if=/dev/zero of=$image bs=$size count=1 kvmx_sudo_run qemu-img create -f raw $image $size device="`sudo losetup --find --show $image`" echo "Partitioning image at $device..." kvmx_sudo_run parted -s -- $device mklabel gpt kvmx_sudo_run parted -s -- $device unit MB mkpart non-fs 2 3 kvmx_sudo_run parted -s -- $device set 1 bios_grub on kvmx_sudo_run parted -s -- $device unit MB mkpart ext2 3 -1 kvmx_sudo_run parted -s -- $device set 2 boot on kvmx_sudo_run mkfs.ext4 ${device}p2 kvmx_sudo_run mount ${device}p2 $WORK/ # Non-interactive installation APT_INSTALL="kvmx_sudo_run LC_ALL=C DEBIAN_FRONTEND=noninteractive chroot $WORK/ apt-get install -y" # Initial system install. echo "Installing base system..." kvmx_sudo_run LC_ALL=C DEBIAN_FRONTEND=noninteractive debootstrap --arch=$arch $version $WORK/ $mirror # Initial configuration. echo "Applying initial configuration..." kvmx_sudo_run mount none -t proc $WORK/proc kvmx_sudo_run mount none -t sysfs $WORK/sys kvmx_sudo_run mount -o bind /dev/ $WORK/dev # Hostname configuration. echo $hostname.$domain | $SUDO tee $WORK/etc/hostname > /dev/null echo "127.0.0.1 localhost" | $SUDO tee -a $WORK/etc/hosts > /dev/null # This ordering is important for facter correctly guess the domain name echo "127.0.0.1 $hostname.$domain $hostname" | $SUDO tee -a $WORK/etc/hosts > /dev/null # Invert hostname contents to avoid http://projects.puppetlabs.com/issues/2533 tac $WORK/etc/hosts | $SUDO tee $WORK/etc/hosts.new > /dev/null kvmx_sudo_run mv $WORK/etc/hosts.new $WORK/etc/hosts # Fstab echo "/dev/vda2 / ext4 errors=remount-ro 0 1" | $SUDO tee $WORK/etc/fstab > /dev/null # Locale $APT_INSTALL locales echo "LANG=$LANG" | $SUDO tee $WORK/etc/default/locale > /dev/null echo "$LANG UTF-8" | $SUDO tee -a $WORK/etc/locale.gen > /dev/null kvmx_sudo_run chroot $WORK/ locale-gen # Initial upgrade echo "Applying initial upgrades..." kvmx_sudo_run chroot $WORK/ apt-get update kvmx_sudo_run chroot $WORK/ apt-get upgrade -y if [ "$arch" == "i386" ]; then kernel_arch="686" else kernel_arch="$arch" fi # Basic packages $APT_INSTALL screen cron lsb-release openssl rsync -y $APT_INSTALL spice-vdagent qemu-guest-agent # Kernel $APT_INSTALL linux-image-$kernel_arch -y # OpenSSH $APT_INSTALL openssh-server -y kvmx_sudo_run chroot $WORK/ service ssh stop # Sudo echo "Installing sudo..." $APT_INSTALL sudo -y echo "%sudo ALL=NOPASSWD: ALL" | $SUDO tee $WORK/etc/sudoers.d/local > /dev/null # Root password echo 'root:root' | kvmx_sudo_run chroot $WORK/ chpasswd # Initial user kvmx_sudo_run chroot $WORK/ useradd $user -G sudo -s /bin/bash if [ "$ssh_support" == "y" ]; if [ "$ssh_custom" == "y" ]; then privkey="`dirname $image`/ssh/`basename $image .img`.key" pubkey="${privkey}.pub" mkdir -p "`dirname $privkey`" $DIRNAME/kvmx-keygen $privkey "$user@`basename $image .img`" else pubkey="$DIRNAME/share/ssh/insecure_private_key.pub" fi kvmx_sudo_run chroot $WORK/ mkdir -p /home/user/.ssh kvmx_sudo_run chroot $WORK/ chmod 700 /home/user/.ssh kvmx_sudo_run cp $pubkey $WORK/home/user/.ssh/authorized_keys kvmx_sudo_run chroot $WORK/ chmod 600 /home/user/.ssh/authorized_keys kvmx_sudo_run touch $WORK/home/user/.hushlogin fi kvmx_sudo_run chroot $WORK/ chown -R user.user /home/user echo "$user:$password" | kvmx_sudo_run chroot $WORK/ chpasswd # Networking cat <<-EOF | $SUDO tee $WORK/etc/network/interfaces.d/ens3 > /dev/null allow-hotplug ens3 iface ens3 inet dhcp EOF # Grub $APT_INSTALL grub-pc -y kvmx_sudo_run chroot $WORK/ update-grub kvmx_sudo_run chroot $WORK/ grub-install $device # Teardown kvmx_sudo_run umount $WORK/proc kvmx_sudo_run umount $WORK/sys kvmx_sudo_run umount $WORK/dev kvmx_sudo_run umount $WORK kvmx_sudo_run rmdir $WORK kvmx_sudo_run losetup -d $device # Image conversion if [ "$format" == "qcow2" ]; then echo "Converting raw image to qcow2..." kvmx_sudo_run mv $image $image.raw kvmx_sudo_run qemu-img convert -O qcow2 ${image}.raw $image kvmx_sudo_run rm ${image}.raw fi # Fix permissions kvmx_sudo_run chown -R `whoami`. `dirname $image` } # Dispatch if [ "$method" == "custom" ]; then kvmx_create_custom elif [ "$method" == "vmdebootstrap" ]; then kvmx_create_vmdebootstrap else echo "$BASENAME: invalid method $method" exit 1 fi