#!/bin/bash
### Initialize
set -u
set -e
TESTING=
DO_SHOWSPEC=y
DO_VIRTINSTALL=
scripts=$(dirname $0)/scripts
. $scripts/lib.sh
### Command-line input
## Command-line options
function _help () {
cat <<END
Usage: $1 [options] <name> [param=value [...]]
Options:
-h Show this help text
-D Debug
-v Verbose
-q Quiet
-N Mock command execution
-P Skip command prompts
-I Do a virt-install
-T Just testing..
Parameters are given as:
name=value-{FOO}
END
}
while getopts "hDvqNPTI" opt; do
case "$opt" in
h)
_help $0
exit 0
;;
D)
LOG_DEBUG=y
LOG_CMD=y
;;
v)
LOG_CMD=y
log_debug "log: Commands"
;;
q)
LOG_CMD=
LOG_INFO=
DO_SHOWSPEC=
log_debug "log: Quiet"
;;
N)
log_info "Mock-executing commands (this will break functionality..)"
CMD_MOCK=y
;;
P)
log_info "Skipping command confirmations"
CMD_PROMPT=
;;
T)
log_info "Just testing.."
TESTING=y
;;
I)
log_info "Do virt-install"
DO_VIRTINSTALL=y
;;
?)
_help $0
exit 1
;;
esac
done
# forget them
[ $OPTIND -gt 1 ] && shift $(( $OPTIND - 1 ))
## Command-line arguments
# Name
[ -z $1 ] && die "Machine name must be given as first argument"
opt_name="$1"; shift
# Defaults
function define_opt () {
local name=$1
local value=${2:-}
log_debugf "%-20s = %s" $name "$value"
eval "opt_${name}=${value}"
}
function resolve_name () {
local name=$1
local out=$(dig +short $name)
[ -z "$out" ] && die "Hostname lookup failed: $name"
echo $out
}
define_opt ram 1G
define_opt cpus 2
define_opt os debiansqueeze
define_opt disk_size 10G
define_opt disk_vg pvl
define_opt disk_lv $opt_name
define_opt disk_bus virtio
define_opt guest_disk /dev/vda
define_opt hostname $opt_name
define_opt bridge br-lan
define_opt domain paivola.fi
define_opt ip
define_opt user $USER
define_opt puppet
define_opt puppet_master puppet
define_opt serial_console
log_info "Processing ${#@} parameters:"
for param in "$@"; do
name=${param%=*}
value=${param##*=}
# evaluate
value=$(expand_line "$value")
define_opt ${name} "${value}"
done
# resolve defaults
if [ -z $opt_ip ]; then
define_opt ip $(resolve_name ${opt_name}.${opt_domain})
fi
### Virtual machine config
## General
NAME=$opt_name
DOMAIN=$opt_domain
FQDN=${NAME}.${DOMAIN}
## libvirt guest info
# Name
GUEST_NAME=$NAME
# Basic params
GUEST_RAM=$opt_ram
GUEST_VCPUS=$opt_cpus
# OS variant (for virt-install)
GUEST_OS_VARIANT=$opt_os
## Disk
# Size of LV to create
DISK_SIZE=$opt_disk_size
# LVM vg to use
DISK_VG=$opt_disk_vg
# LVM lv to use
DISK_NAME=$opt_disk_lv
# Path to disk block device
DISK_PATH=/dev/mapper/${DISK_VG}-${DISK_NAME}
GUEST_DISK_BUS=$opt_disk_bus
## Serial
# Serial console?
# XXX: hardcoded as ttyS0
case x"$opt_serial_console" in
# xtty*) SERIAL_CONSOLE="$opt_serial_console" ;;
x) SERIAL_CONSOLE= ;;
x*) SERIAL_CONSOLE="ttyS0" ;;
esac
### Preseed content
GUEST_DISK=$opt_guest_disk
## Network
# Network configuration, for /etc/network/interfaces
NET_DOMAIN=${DOMAIN}
NET_HOSTNAME=${NAME}
NET_BRIDGE=$opt_bridge
NET_IPADDR=$opt_ip
NET_NETMASK=255.255.255.0
NET_GATEWAY=194.197.235.1
NET_NAMESERVERS=( 194.197.235.210 194.197.235.252 )
## Clock/time
TIME_ZONE='Europe/Helsinki'
# only used during install, not stored in target
TIME_NTP_SERVER=ntp.paivola.fi # XXX: harcoded
## User account
function getent_user_attr () {
local user=$1
local db=$2
local attr=$3
line=$(getent $db $user) || die "Unable to read $db database for $user"
echo "$line" | cut -d ':' -f $attr
}
function user_fullname () {
local user=$1
getent_user_attr $user passwd 5
}
function user_shadow () {
local user=$1
if [ $UID -eq 0 ]; then
log_debug "Get user password from shadow: $user"
getent_user_attr $user shadow 2
else
echo -n "Install target login ($USER_NAME) " >&2
mkpasswd -m sha-512
fi
}
# per opt
case x"$opt_user" in
x)
log_info "user: Skipping user account creation"
USER_CREATE='false'
USER_NAME=
USER_FULLNAME=
USER_SHADOW=
;;
x*)
log_info "user: With username=${opt_user}"
USER_CREATE='true'
USER_NAME=$opt_user
USER_FULLNAME=$(user_fullname $opt_user)
USER_SHADOW=$(user_shadow $opt_user)
;;
esac
USER_GROUPS=( cdrom sudo adm )
### Installer setup
## Installation image
# Original Debian Installer image (iso)
INSTALLER_NAME="debian-6.0.3-amd64"
INSTALLER_ISO="iso/${INSTALLER_NAME}-netinst.iso"
INSTALLER_TREE="images/$INSTALLER_NAME"
INSTALLER_FLAG="${INSTALLER_TREE}.unpacked"
# Customized preseed image name
INSTALL_NAME="debian-6.0.3-amd64_${GUEST_NAME}"
# Customized image content
INSTALL_TREE="images/${INSTALL_NAME}"
INSTALL_ISO="iso/${INSTALL_NAME}.iso"
### Preseed setup
## preseed.cfg templating
PRESEED_DIR="preseed"
# Preseed output file in install tree
PRESEED_NAME="preseed.cfg"
# Mount path of preseed target in installer
PRESEED_MOUNT="/cdrom"
# Prefix for target files in install tree
# XXX: not implemented
#PRESEED_TARGET_PREFIX=""
# Main preseed source template
PRESEED_TEMPLATE="${PRESEED_DIR}/${PRESEED_NAME}"
# Target path for preseed in install tree
PRESEED_FILE="${INSTALL_TREE}/${PRESEED_NAME}"
# Checksum of target preseed.cfg
PRESEED_CHECKSUM= # set later
## preseed.cfg contents
# List of additional packages to install
PRESEED_PACKAGES=( sudo screen vim )
# Script commands to execute
PRESEED_LATE_COMMANDS=( )
PRESEED_LATE_COMMANDS_END=( )
# Chainload preseed files
PRESEED_INCLUDES=( 'passwords.cfg' 'host.cfg' )
# Add packages to preseed install
function preseed_packages () {
PRESEED_PACKAGES=( ${PRESEED_PACKAGES[@]} "$@" )
}
# Add command to execute
function preseed_late_commands () {
PRESEED_LATE_COMMANDS=( "${PRESEED_LATE_COMMANDS[@]:-}" "$@" )
}
# Add command to execute at end
# XXX: ordering?
function preseed_late_commands_end () {
PRESEED_LATE_COMMANDS_END=( "${PRESEED_LATE_COMMANDS_END[@]:-}" "$@" )
}
## preseed-files
# template source
PRESEED_FILES_SOURCES=( 'preseed/files' )
# template output into install tree
PRESEED_FILES_TARGET_NAME='preseed-files'
PRESEED_FILES_TARGET="${INSTALL_TREE}/${PRESEED_FILES_TARGET_NAME}"
# paths within installer runtime
PRESEED_FILES_INSTALLER_SOURCE="${PRESEED_MOUNT}/${PRESEED_FILES_TARGET_NAME}"
PRESEED_FILES_INSTALLER_TARGET='/target'
# Add a tree of configuration files to template into the installer
function preseed_conf_files () {
PRESEED_FILES_SOURCES=( "${PRESEED_FILES_SOURCES[@]:-}" "$@" )
}
# Add a file to install in preseed, without templating
# preseed_file <src> <dst>/
# if dst is a dir, it must end in /
function preseed_file () {
local src=$1
local dst=$2
local dir=$(dirname $dst)
local tgt=${PRESEED_FILES_TARGET}
local tgt_dir="$tgt/$dir"
if [ ! -d "$tgt_dir" ]; then
cmd mkdir -p "$tgt_dir"
fi
cmd cp "$src" "$tgt/$dst"
}
### Extra
## Puppet
PUPPET=
if [ $opt_puppet ]; then
log_info "Puppetizing preseed"
PUPPET=yes
PUPPET_PRESEED_DIR="${PRESEED_DIR}/puppet"
## Packages
preseed_packages puppet
## Vars for preseed-files
# hostname for puppetmaster (server)
PUPPET_MASTER="${opt_puppet_master}"
# path ssl data (ssldir)
PUPPET_SSLDIR=/etc/puppet/ssl
## Preseed files
# add to list of conf files to copy
preseed_conf_files "${PUPPET_PRESEED_DIR}/files"
fi
# Invoked during image-customizing process
function puppet_config () {
## Preseed ssl certs?
PUPPET_SOURCE_SSLDIR="${PUPPET_PRESEED_DIR}/ssl"
# copy file to preseed if exists
function puppet_preseed_ssl_file () {
local name=$1
local src=${PUPPET_SOURCE_SSLDIR}/$name
local dst=${PUPPET_SSLDIR}/$name
if [ -f $src ]; then
log_info "puppet: preseed ssl data: $name"
cmd preseed_file $src $dst
else
log_debug "puppet: skip ssl preseed: $name"
fi
}
# ca.pem
puppet_preseed_ssl_file certs/ca.pem
# guest cert/pkey
puppet_preseed_ssl_file certs/${FQDN}.pem
puppet_preseed_ssl_file private_keys/${FQDN}.pem
}
## Configure GRUB, via preseed/files: /etc/default/grub
# Kernel commandline/grub terminal
# The last console=... for kernel is used as /dev/console, i.e. init output
if [ $SERIAL_CONSOLE ]; then
BOOT_KERNEL_CONSOLE="console=${SERIAL_CONSOLE} console=tty0"
BOOT_GRUB_TERMINAL="console serial"
# re-generate grub.cfg
preseed_late_commands_end "in-target update-grub"
# Configure /etc/inittab for serial console
preseed_late_commands "in-target sed -i 's/#T0/T0/' /etc/inittab"
else
BOOT_KERNEL_CONSOLE=""
BOOT_GRUB_TERMINAL="console"
fi
# Kernel boot args (overrides those generated by installer) - default boot option uses args + args_default
BOOT_KERNEL_ARGS_DEFAULT="quiet"
BOOT_KERNEL_ARGS="${BOOT_KERNEL_CONSOLE}"
### Postprocess preseed
## Preseed files
## Preseed / config files
# copy at end of install
preseed_late_commands "cp -rd -- ${PRESEED_FILES_INSTALLER_SOURCE}/* ${PRESEED_FILES_INSTALLER_TARGET}"
# Additional files to copy
PRESEED_INCLUDE_FILES=( $(for preseed in ${PRESEED_INCLUDES[@]}; do echo "${PRESEED_DIR}/${preseed}"; done) )
#PRESEED_INCLUDE_FILES=("preseed/passwords.cfg" "preseed/host.cfg")
# preseed.cfg 'includes' line
PRESEED_INCLUDE=${PRESEED_INCLUDES[@]}
# preseed command execution
PRESEED_LATE_COMMAND=$(for cmd in "${PRESEED_LATE_COMMANDS[@]}" "${PRESEED_LATE_COMMANDS_END[@]:-}"; do if [ "$cmd" ]; then echo -n "$cmd;" $'\\\n '; fi; done; echo true)
## Isolinux
# Source for install tree isolinux file
PRESEED_ISOLINUX="${PRESEED_DIR}/isolinux.cfg"
# Automagics for serial boot :)
# virt-install assumes we have an X $DISPLAY if given --vnc
# but we also want to enable the serial console...
# d-i automagically configures the serial boot/login console, but only if the installer is run under serial...
# we want to have both :(
if [ ${DISPLAY:-} ]; then
INSTALL_BOOT_CONSOLE=""
else
INSTALL_BOOT_CONSOLE="console=ttyS0"
fi
# isolinux installer boot args
INSTALL_BOOT_ARGS="auto=true priority=critical preseed/file=${PRESEED_MOUNT}/${PRESEED_NAME} preseed/file/checksum=${PRESEED_CHECKSUM} -- ${INSTALL_BOOT_CONSOLE} quiet"
### External progs
## Bootable .iso for Debian isolinux-based installer CDs
GENISOIMAGE=/usr/bin/genisoimage
GENISOIMAGE_OPTS="-r -J -no-emul-boot -boot-load-size 4 -boot-info-table -b isolinux/isolinux.bin -c isolinux/boot.cat"
## LVM
LVM=/sbin/lvm
## Libvirt
# --connect URL
LIBVIRT=qemu:///system
VIRSH=/usr/bin/virsh
function virsh () {
$VIRSH --connect $LIBVIRT "$@"
}
# type of guest to create
LIBVIRT_TYPE=kvm
## virt-install
VIRT_INSTALL="/usr/bin/virt-install"
## SELinux?
#SEMANAGE=/usr/sbin/semanage
#RESTORECON=/sbin/restorecon
### Prepare
if [ $DO_SHOWSPEC ]; then
cat <<END
Guest:
Name: $GUEST_NAME
CPUs: $GUEST_VCPUS
RAM: $GUEST_RAM
OS: $GUEST_OS_VARIANT
Disk: $GUEST_DISK
Boot:
Serial console: $SERIAL_CONSOLE
Grub terminal: $BOOT_GRUB_TERMINAL
Kernel args: $BOOT_KERNEL_ARGS ($BOOT_KERNEL_ARGS_DEFAULT)
Disk:
Method: LVM
Size: $DISK_SIZE
LVM:
VG: $DISK_VG
LV: $DISK_NAME
Path: $DISK_PATH
Bus: $GUEST_DISK_BUS
Net:
Hostname: $NET_HOSTNAME
Method: Bridge
IP: $NET_IPADDR
Bridge:
Name: $NET_BRIDGE
User:
username: $USER_NAME
fullname: $USER_FULLNAME
shadow: $USER_SHADOW
Installer:
Name: $INSTALLER_NAME
ISO: $INSTALLER_ISO
Tree: $INSTALLER_TREE
Install:
Name: $INSTALL_NAME
Tree: $INSTALL_TREE
ISO: $INSTALL_ISO
Preseed:
Name: $PRESEED_NAME
Template: $PRESEED_TEMPLATE
Target: $PRESEED_FILE
Isolinux: $PRESEED_ISOLINUX
Includes: $PRESEED_INCLUDES
END
fi
[ $TESTING ] && exit 0
### Check
# Parameters given?
[ -z $NET_IPADDR ] && die "net: No IP-address given: ip"
# Installer exists?
if cmd test ! -f ${INSTALLER_ISO}; then
die "Installer not found: ${INSTALLER_ISO}"
fi
### Prepare install
log_info "Extract installer..."
## Extract .iso
if [ -f ${INSTALLER_FLAG} ]; then
log_info "Installer already unpacked: ${INSTALLER_TREE}"
else
log_info "Unpacking installer: ${INSTALLER_ISO}"
cmd extract_iso ${INSTALLER_ISO} ${INSTALLER_TREE}
cmd touch ${INSTALLER_FLAG}
fi
## Copy to customized tree
[ -d ${INSTALL_TREE} ] && cmd rm -r ${INSTALL_TREE}
cmd cp -rd ${INSTALLER_TREE} ${INSTALL_TREE}
cmd chmod -R u=rwX,og=rX ${INSTALL_TREE}
log_info "Installer extracted: $INSTALL_TREE"
### Customize preseed
log_info "Generate preseed..."
## preseed.cfg
cmd expand_template ${PRESEED_TEMPLATE} ${PRESEED_FILE}
# md5sum
PRESEED_CHECKSUM=$(my_md5sum $PRESEED_FILE)
## Isolinux
cmd expand_template ${PRESEED_ISOLINUX} ${INSTALL_TREE}/isolinux/isolinux.cfg
## Includes
for file in ${PRESEED_INCLUDE_FILES[@]}; do
name=$(basename $file)
cmd expand_template $file ${INSTALL_TREE}/${name}
done
log_debug "Preseed generated: $PRESEED_FILE"
## Config preseed-files
log_info "Copy preseed-files..."
for tree in "${PRESEED_FILES_SOURCES[@]}"; do
log_info "preseed-files: $tree"
cmd expand_tree $tree ${PRESEED_FILES_TARGET}
done
## Modules
# Puppet
[ $PUPPET ] && puppet_config
### Create .iso
log_info "Generate ISO..."
[ -f ${INSTALL_ISO} ] && cmd rm -f ${INSTALL_ISO}
# generates a lot of output
cmd ${GENISOIMAGE} -o ${INSTALL_ISO} -quiet ${GENISOIMAGE_OPTS} ${INSTALL_TREE}
log_info "Install ISO generated: $INSTALL_ISO"
### Create virtual machine
if [ $DO_VIRTINSTALL ]; then
log_info "Create VM..."
## Check
# VM exists?
if cmd virsh domid ${GUEST_NAME} > /dev/null 2> /dev/null; then
die "Virtual machine already exists: ${GUEST_NAME}"
fi
## Disk
# Create LV (unless it already exists)
[ -e $DISK_PATH ] || cmd_confirm sudo $LVM lvcreate -L $DISK_SIZE -n $DISK_NAME $DISK_VG
## SELinux?
#$SEMANAGE fcontext -a -t virt_image_t $DISK
#$RESTORECON -v $DISK
## virt-install
cmd_confirm $VIRT_INSTALL \
--connect $LIBVIRT \
--name $GUEST_NAME \
--ram $(expand_MB $GUEST_RAM) --vcpus $GUEST_VCPUS \
--cdrom "$INSTALL_ISO" \
--os-variant $GUEST_OS_VARIANT \
--disk path=$DISK_PATH,bus=$GUEST_DISK_BUS \
--network bridge:$NET_BRIDGE \
--virt-type $LIBVIRT_TYPE \
--accelerate --hvm \
--vnc \
--serial pty
fi