terom@0: #!/bin/bash terom@0: terom@7: ### Initialize terom@7: set -u terom@7: set -e terom@0: terom@7: TESTING= terom@7: terom@9: DO_SHOWSPEC=y terom@7: DO_VIRTINSTALL= terom@7: terom@24: lib=$(dirname $0)/lib terom@24: . $lib/lib.sh terom@7: terom@7: ### Command-line input terom@7: ## Command-line options terom@7: function _help () { terom@7: cat < [param=[value] [...]] terom@7: terom@7: Options: terom@7: -h Show this help text terom@7: terom@7: -D Debug terom@7: -v Verbose terom@7: -q Quiet terom@7: terom@7: -N Mock command execution terom@7: -P Skip command prompts terom@7: terom@7: -I Do a virt-install terom@7: terom@7: -T Just testing.. terom@7: terom@7: Parameters are given as: terom@7: terom@14: name=value-{FOO} terom@7: terom@7: END terom@7: } terom@7: terom@7: while getopts "hDvqNPTI" opt; do terom@7: case "$opt" in terom@7: h) terom@7: _help $0 terom@7: terom@7: exit 0 terom@7: ;; terom@7: terom@7: D) terom@7: LOG_DEBUG=y terom@7: LOG_CMD=y terom@7: terom@7: ;; terom@7: terom@7: v) terom@7: LOG_CMD=y terom@7: terom@7: log_debug "log: Commands" terom@7: ;; terom@7: terom@7: q) terom@7: LOG_CMD= terom@7: LOG_INFO= terom@9: DO_SHOWSPEC= terom@7: terom@7: log_debug "log: Quiet" terom@7: ;; terom@7: terom@7: N) terom@7: log_info "Mock-executing commands (this will break functionality..)" terom@7: terom@7: CMD_MOCK=y terom@7: ;; terom@7: terom@7: P) terom@7: log_info "Skipping command confirmations" terom@7: terom@7: CMD_PROMPT= terom@7: ;; terom@7: terom@7: T) terom@7: log_info "Just testing.." terom@7: terom@7: TESTING=y terom@7: ;; terom@7: terom@7: I) terom@7: log_info "Do virt-install" terom@7: terom@7: DO_VIRTINSTALL=y terom@7: ;; terom@7: ?) terom@7: _help $0 terom@7: terom@7: exit 1 terom@7: ;; terom@7: esac terom@7: done terom@7: terom@9: terom@7: # forget them terom@9: [ $OPTIND -gt 1 ] && shift $(( $OPTIND - 1 )) terom@7: terom@7: ## Command-line arguments terom@8: # Name terom@8: [ -z $1 ] && die "Machine name must be given as first argument" terom@8: terom@8: opt_name="$1"; shift terom@8: terom@8: # Defaults terom@8: function define_opt () { terom@8: local name=$1 terom@8: local value=${2:-} terom@8: terom@8: log_debugf "%-20s = %s" $name "$value" terom@8: eval "opt_${name}=${value}" terom@8: } terom@8: terom@8: function resolve_name () { terom@8: local name=$1 terom@8: local out=$(dig +short $name) terom@8: terom@8: [ -z "$out" ] && die "Hostname lookup failed: $name" terom@8: terom@8: echo $out terom@8: } terom@8: terom@8: define_opt ram 1G terom@8: define_opt cpus 2 terom@8: define_opt os debiansqueeze terom@8: define_opt disk_size 10G terom@8: define_opt disk_vg pvl terom@8: define_opt disk_lv $opt_name terom@8: define_opt disk_bus virtio terom@8: define_opt guest_disk /dev/vda terom@8: define_opt hostname $opt_name terom@8: define_opt bridge br-lan terom@18: define_opt domain paivola.fi terom@9: define_opt ip terom@21: define_opt user $USER terom@14: define_opt puppet terom@15: define_opt puppet_master puppet terom@25: define_opt serial_console yes terom@8: terom@8: log_info "Processing ${#@} parameters:" terom@7: for param in "$@"; do terom@7: name=${param%=*} terom@7: value=${param##*=} terom@7: terom@7: # evaluate terom@7: value=$(expand_line "$value") terom@7: terom@8: define_opt ${name} "${value}" terom@7: done terom@7: terom@9: # resolve defaults terom@9: if [ -z $opt_ip ]; then terom@18: define_opt ip $(resolve_name ${opt_name}.${opt_domain}) terom@9: fi terom@9: terom@14: ### Virtual machine config terom@18: ## General terom@18: NAME=$opt_name terom@18: DOMAIN=$opt_domain terom@18: FQDN=${NAME}.${DOMAIN} terom@18: terom@14: ## libvirt guest info terom@14: # Name terom@18: GUEST_NAME=$NAME terom@1: terom@1: # Basic params terom@8: GUEST_RAM=$opt_ram terom@8: GUEST_VCPUS=$opt_cpus terom@1: terom@1: # OS variant (for virt-install) terom@8: GUEST_OS_VARIANT=$opt_os terom@1: terom@31: # I/O terom@31: GUEST_DISK_BUS=$opt_disk_bus terom@31: terom@1: ## Disk terom@1: # Size of LV to create terom@8: DISK_SIZE=$opt_disk_size terom@0: terom@1: # LVM vg to use terom@8: DISK_VG=$opt_disk_vg terom@0: terom@1: # LVM lv to use terom@8: DISK_NAME=$opt_disk_lv terom@0: terom@1: # Path to disk block device terom@1: DISK_PATH=/dev/mapper/${DISK_VG}-${DISK_NAME} terom@14: terom@16: ## Serial terom@16: # Serial console? terom@16: # XXX: hardcoded as ttyS0 terom@16: case x"$opt_serial_console" in terom@16: # xtty*) SERIAL_CONSOLE="$opt_serial_console" ;; terom@16: x) SERIAL_CONSOLE= ;; terom@31: x*) SERIAL_CONSOLE="ttyS0" ;; terom@16: esac terom@16: terom@14: ### Preseed content terom@1: ## Network terom@13: # Network configuration, for /etc/network/interfaces terom@18: NET_DOMAIN=${DOMAIN} terom@18: NET_HOSTNAME=${NAME} terom@8: NET_BRIDGE=$opt_bridge terom@8: NET_IPADDR=$opt_ip terom@13: NET_NETMASK=255.255.255.0 terom@13: NET_GATEWAY=194.197.235.1 terom@13: NET_NAMESERVERS=( 194.197.235.210 194.197.235.252 ) terom@1: terom@13: ## Clock/time terom@13: TIME_ZONE='Europe/Helsinki' terom@13: terom@13: # only used during install, not stored in target terom@13: TIME_NTP_SERVER=ntp.paivola.fi # XXX: harcoded terom@13: terom@13: ## User account terom@13: function getent_user_attr () { terom@13: local user=$1 terom@13: local db=$2 terom@13: local attr=$3 terom@13: terom@13: line=$(getent $db $user) || die "Unable to read $db database for $user" terom@13: terom@13: echo "$line" | cut -d ':' -f $attr terom@13: } terom@13: function user_fullname () { terom@13: local user=$1 terom@13: terom@13: getent_user_attr $user passwd 5 terom@13: } terom@13: function user_shadow () { terom@13: local user=$1 terom@13: terom@13: if [ $UID -eq 0 ]; then terom@13: log_debug "Get user password from shadow: $user" terom@13: getent_user_attr $user shadow 2 terom@13: else terom@14: echo -n "Install target login ($USER_NAME) " >&2 terom@13: mkpasswd -m sha-512 terom@13: fi terom@13: } terom@13: terom@21: # per opt terom@21: case x"$opt_user" in terom@29: x|xroot) terom@21: log_info "user: Skipping user account creation" terom@21: terom@21: USER_CREATE='false' terom@21: USER_NAME= terom@21: USER_FULLNAME= terom@21: USER_SHADOW= terom@21: terom@21: ;; terom@21: terom@21: x*) terom@21: log_info "user: With username=${opt_user}" terom@21: terom@21: USER_CREATE='true' terom@21: USER_NAME=$opt_user terom@21: USER_FULLNAME=$(user_fullname $opt_user) terom@21: USER_SHADOW=$(user_shadow $opt_user) terom@21: terom@21: ;; terom@21: esac terom@21: terom@14: USER_GROUPS=( cdrom sudo adm ) terom@13: terom@25: ### Paths terom@18: ## Installation image terom@18: # Original Debian Installer image (iso) terom@37: BASE_INSTALLER_VERSION="6.0.5" terom@34: BASE_INSTALLER_NAME="debian-${BASE_INSTALLER_VERSION}-amd64" terom@25: BASE_INSTALLER_ISO="iso/${BASE_INSTALLER_NAME}-netinst.iso" terom@25: BASE_INSTALLER_TREE="images/$BASE_INSTALLER_NAME" terom@25: BASE_INSTALLER_FLAG="${BASE_INSTALLER_TREE}.unpacked" terom@18: terom@25: terom@25: ## preseed config templates terom@25: # Source files for preseeding terom@25: PRESEED_DIR="preseed" terom@25: PRESEED_NAME="preseed.cfg" terom@25: PRESEED_FILE="${PRESEED_DIR}/${PRESEED_NAME}" terom@25: terom@25: # Source for isolinux bootloader config terom@25: PRESEED_ISOLINUX_FILE="${PRESEED_DIR}/isolinux.cfg" terom@25: terom@25: terom@25: ## Installer tree terom@18: # Customized preseed image name terom@34: INSTALL_NAME="debian-${BASE_INSTALLER_VERSION}-amd64_${NAME}" terom@18: terom@18: # Customized image content terom@18: INSTALL_TREE="images/${INSTALL_NAME}" terom@22: INSTALL_ISO="iso/${INSTALL_NAME}.iso" terom@18: terom@25: # Preseed data in installer terom@25: INSTALL_PRESEED_DIR_NAME="preseed" terom@25: INSTALL_PRESEED_DIR="${INSTALL_TREE}/${INSTALL_PRESEED_DIR_NAME}" terom@25: INSTALL_PRESEED_FILE_NAME="${NAME}.cfg" terom@25: INSTALL_PRESEED_FILE="${INSTALL_PRESEED_DIR}/${INSTALL_PRESEED_FILE_NAME}" terom@18: terom@18: # Checksum of target preseed.cfg terom@25: INSTALL_PRESEED_CHECKSUM= # set later terom@25: terom@25: # isolinux.cfg for bootloader terom@25: INSTALL_ISOLINUX_FILE="${INSTALL_TREE}/isolinux/isolinux.cfg" terom@25: terom@25: ## Paths within installer runtime terom@25: # Paths to preseed files from within installer runtime terom@25: INSTALLER_MOUNT="/cdrom" terom@25: INSTALLER_PRESEED_DIR="${INSTALLER_MOUNT}/${INSTALL_PRESEED_DIR_NAME}" terom@25: INSTALLER_PRESEED_FILE="${INSTALLER_PRESEED_DIR}/${INSTALL_PRESEED_FILE_NAME}" terom@25: terom@25: terom@27: ## Stage dir, for generating files for INSTALL_PRESEED terom@27: STAGE_ROOT="stage" terom@27: STAGE_DIR="${STAGE_ROOT}/${NAME}" terom@18: terom@27: log_info "Stage: prepare stage dir" terom@27: terom@27: # root terom@27: [ ! -d ${STAGE_ROOT} ] && cmd mkdir ${STAGE_ROOT} terom@27: terom@27: # host-specific; clean terom@27: [ -d ${STAGE_DIR} ] && cmd rm -r ${STAGE_DIR} terom@27: cmd mkdir ${STAGE_DIR} terom@18: terom@31: function stage_file () { terom@31: local src=$1 terom@31: local name=$2 terom@31: terom@31: local tgt=${STAGE_DIR}/$name terom@31: terom@31: log_debug "stage: file: $name" terom@31: cmd cp $src $tgt terom@31: } terom@31: terom@31: ### preseed.cfg contents terom@31: terom@31: ## Disk terom@31: # Disk to install to terom@31: PARTMAN_DISK=$opt_guest_disk terom@31: terom@31: # regular/lvm/crypto terom@31: PARTMAN_METHOD=regular terom@31: terom@31: # Recipe to use terom@31: PARTMAN_RECIPE='Trvial' terom@31: terom@31: # recipe file terom@31: PARTMAN_RECIPE_FILE_NAME="partman.recipe" terom@31: PRESEED_PARTMAN_RECIPE_FILE="${PRESEED_DIR}/${PARTMAN_RECIPE_FILE_NAME}" terom@31: INSTALL_PARTMAN_RECIPE_FILE_NAME="${PARTMAN_RECIPE_FILE_NAME}" terom@31: INSTALLER_PARTMAN_RECIPE_FILE="${INSTALLER_PRESEED_DIR}/${PARTMAN_RECIPE_FILE_NAME}" terom@31: terom@31: # deploy terom@31: stage_file ${PRESEED_PARTMAN_RECIPE_FILE} ${INSTALL_PARTMAN_RECIPE_FILE_NAME} terom@31: terom@31: # path for d-i to find it terom@31: PARTMAN_RECIPE_FILE="${INSTALLER_PARTMAN_RECIPE_FILE}" terom@31: terom@31: ## Misc terom@18: # List of additional packages to install terom@18: PRESEED_PACKAGES=( sudo screen vim ) terom@18: terom@18: # Script commands to execute terom@14: PRESEED_LATE_COMMANDS=( ) terom@18: terom@18: # Chainload preseed files terom@14: PRESEED_INCLUDES=( 'passwords.cfg' 'host.cfg' ) terom@13: terom@18: # Add packages to preseed install terom@18: function preseed_packages () { terom@18: PRESEED_PACKAGES=( ${PRESEED_PACKAGES[@]} "$@" ) terom@18: } terom@18: terom@18: # Add command to execute terom@16: function preseed_late_commands () { terom@16: PRESEED_LATE_COMMANDS=( "${PRESEED_LATE_COMMANDS[@]:-}" "$@" ) terom@16: } terom@16: terom@27: ## preseed-files terom@27: # staged files terom@27: # name must match with INSTALL_PRESEED_FILES_DIR terom@27: STAGE_PRESEED_FILES_DIR="${STAGE_DIR}/files" terom@16: terom@27: log_info "Stage: prepare preseed-files" terom@27: cmd mkdir "${STAGE_PRESEED_FILES_DIR}" terom@15: terom@25: # template source trees terom@25: PRESEED_FILES_SOURCES=( "${PRESEED_DIR}/files" ) terom@18: terom@18: # template output into install tree terom@25: INSTALL_PRESEED_FILES_DIR_NAME="${INSTALL_PRESEED_DIR_NAME}/files" terom@25: INSTALL_PRESEED_FILES_DIR="${INSTALL_TREE}/${INSTALL_PRESEED_FILES_DIR_NAME}" terom@18: terom@18: # paths within installer runtime terom@25: INSTALLER_PRESEED_FILES_DIR="${INSTALLER_MOUNT}/${INSTALL_PRESEED_FILES_DIR_NAME}" terom@25: INSTALLER_PRESEED_FILES_TARGET='/target' terom@20: terom@20: # Add a tree of configuration files to template into the installer terom@25: function preseed_files_sources () { terom@20: PRESEED_FILES_SOURCES=( "${PRESEED_FILES_SOURCES[@]:-}" "$@" ) terom@20: } terom@18: terom@18: # Add a file to install in preseed, without templating terom@18: # preseed_file / terom@18: # if dst is a dir, it must end in / terom@18: function preseed_file () { terom@18: local src=$1 terom@18: local dst=$2 terom@18: local dir=$(dirname $dst) terom@27: local tgt=${STAGE_PRESEED_FILES_DIR} terom@18: terom@18: local tgt_dir="$tgt/$dir" terom@18: terom@18: if [ ! -d "$tgt_dir" ]; then terom@18: cmd mkdir -p "$tgt_dir" terom@18: fi terom@18: terom@18: cmd cp "$src" "$tgt/$dst" terom@18: } terom@18: terom@27: ## preseed-scripts terom@27: STAGE_PRESEED_SCRIPTS_DIR="${STAGE_DIR}/scripts" terom@27: terom@27: cmd mkdir "${STAGE_PRESEED_SCRIPTS_DIR}" terom@27: terom@27: # output into install tree terom@27: INSTALL_PRESEED_SCRIPTS_DIR_NAME="${INSTALL_PRESEED_DIR_NAME}/scripts" terom@27: INSTALL_PRESEED_SCRIPTS_DIR="${INSTALL_TREE}/${INSTALL_PRESEED_SCRIPTS_DIR_NAME}" terom@27: terom@27: # paths within installer runtime terom@27: INSTALLER_PRESEED_SCRIPTS_DIR="${INSTALLER_MOUNT}/${INSTALL_PRESEED_SCRIPTS_DIR_NAME}" terom@27: terom@27: terom@27: # add a preseed script to execute, from stdin terom@27: function preseed_script () { terom@27: local name=$1 terom@27: local path="${STAGE_PRESEED_SCRIPTS_DIR}/$name" terom@27: terom@27: log_debug "preseed script: $name" terom@27: terom@27: # header terom@27: echo '#!/bin/sh' > $path terom@27: terom@27: # from stdin terom@27: cat >> $path terom@27: terom@27: chmod +x $path terom@27: } terom@27: terom@15: ### Extra terom@15: ## Puppet terom@14: if [ $opt_puppet ]; then terom@14: log_info "Puppetizing preseed" terom@20: terom@20: PUPPET_PRESEED_DIR="${PRESEED_DIR}/puppet" terom@14: terom@20: ## Packages terom@18: preseed_packages puppet terom@14: terom@18: ## Vars for preseed-files terom@18: # hostname for puppetmaster (server) terom@15: PUPPET_MASTER="${opt_puppet_master}" terom@15: terom@18: # path ssl data (ssldir) terom@18: PUPPET_SSLDIR=/etc/puppet/ssl terom@20: terom@20: ## Preseed files terom@20: # add to list of conf files to copy terom@25: preseed_files_sources "${PUPPET_PRESEED_DIR}/files" terom@20: terom@18: ## Preseed ssl certs? terom@20: PUPPET_SOURCE_SSLDIR="${PUPPET_PRESEED_DIR}/ssl" terom@18: terom@18: # copy file to preseed if exists terom@18: function puppet_preseed_ssl_file () { terom@18: local name=$1 terom@18: terom@18: local src=${PUPPET_SOURCE_SSLDIR}/$name terom@18: local dst=${PUPPET_SSLDIR}/$name terom@18: terom@18: if [ -f $src ]; then terom@18: log_info "puppet: preseed ssl data: $name" terom@18: terom@18: cmd preseed_file $src $dst terom@18: else terom@18: log_debug "puppet: skip ssl preseed: $name" terom@18: fi terom@18: } terom@18: terom@18: # ca.pem terom@18: puppet_preseed_ssl_file certs/ca.pem terom@18: terom@18: # guest cert/pkey terom@18: puppet_preseed_ssl_file certs/${FQDN}.pem terom@18: puppet_preseed_ssl_file private_keys/${FQDN}.pem terom@27: fi terom@14: terom@16: ## Configure GRUB, via preseed/files: /etc/default/grub terom@16: # Kernel commandline/grub terminal terom@16: # The last console=... for kernel is used as /dev/console, i.e. init output terom@27: log_info "Configure grub / serial console" terom@16: terom@16: if [ $SERIAL_CONSOLE ]; then terom@16: BOOT_KERNEL_CONSOLE="console=${SERIAL_CONSOLE} console=tty0" terom@16: BOOT_GRUB_TERMINAL="console serial" terom@16: terom@27: # Configure /etc/inittab for serial console terom@27: preseed_script '11-serial-console' <> $modules terom@37: END terom@37: terom@25: ### Postprocess preseed data terom@18: ## Preseed / config files terom@14: terom@25: # copy at end of install terom@25: preseed_late_commands "cp -rd -- ${INSTALLER_PRESEED_FILES_DIR}/* ${INSTALLER_PRESEED_FILES_TARGET}" terom@15: terom@27: ## Preseed / scripts terom@27: preseed_late_commands "for script in ${INSTALLER_PRESEED_SCRIPTS_DIR}/*; do \$script; done" terom@27: terom@25: ## preseed.cfg terom@15: terom@14: # Additional files to copy terom@20: PRESEED_INCLUDE_FILES=( $(for preseed in ${PRESEED_INCLUDES[@]}; do echo "${PRESEED_DIR}/${preseed}"; done) ) terom@14: terom@14: # preseed.cfg 'includes' line terom@14: PRESEED_INCLUDE=${PRESEED_INCLUDES[@]} terom@14: terom@15: # preseed command execution terom@27: PRESEED_LATE_COMMAND=$(for cmd in "${PRESEED_LATE_COMMANDS[@]}"; do if [ "$cmd" ]; then echo -n "$cmd;" $'\\\n '; fi; done; echo true) terom@15: terom@14: terom@14: ## Isolinux terom@16: # Automagics for serial boot :) terom@14: # virt-install assumes we have an X $DISPLAY if given --vnc terom@14: # but we also want to enable the serial console... terom@14: # d-i automagically configures the serial boot/login console, but only if the installer is run under serial... terom@14: # we want to have both :( terom@16: if [ ${DISPLAY:-} ]; then terom@25: INSTALLER_BOOT_CONSOLE="" terom@14: else terom@25: INSTALLER_BOOT_CONSOLE="console=ttyS0" terom@14: fi terom@14: terom@16: # isolinux installer boot args terom@25: INSTALLER_BOOT_ARGS="auto=true priority=critical preseed/file=${INSTALLER_PRESEED_FILE} preseed/file/checksum=${INSTALL_PRESEED_CHECKSUM} -- ${INSTALLER_BOOT_CONSOLE} quiet" terom@13: terom@13: ### External progs terom@13: ## Bootable .iso for Debian isolinux-based installer CDs terom@1: GENISOIMAGE=/usr/bin/genisoimage terom@1: GENISOIMAGE_OPTS="-r -J -no-emul-boot -boot-load-size 4 -boot-info-table -b isolinux/isolinux.bin -c isolinux/boot.cat" terom@1: terom@13: ## LVM terom@1: LVM=/sbin/lvm terom@1: terom@13: ## Libvirt terom@13: # --connect URL terom@1: LIBVIRT=qemu:///system terom@1: VIRSH=/usr/bin/virsh terom@1: terom@1: function virsh () { terom@1: $VIRSH --connect $LIBVIRT "$@" terom@1: } terom@1: terom@1: # type of guest to create terom@1: LIBVIRT_TYPE=kvm terom@1: terom@13: ## virt-install terom@1: VIRT_INSTALL="/usr/bin/virt-install" terom@1: terom@1: ## SELinux? terom@0: #SEMANAGE=/usr/sbin/semanage terom@0: #RESTORECON=/sbin/restorecon terom@0: terom@13: terom@13: terom@7: ### Prepare terom@9: if [ $DO_SHOWSPEC ]; then terom@9: cat < /dev/null 2> /dev/null; then terom@13: die "Virtual machine already exists: ${GUEST_NAME}" terom@13: fi terom@13: terom@7: ## Disk terom@7: # Create LV (unless it already exists) terom@30: [ -e $DISK_PATH ] || cmd_sudo $LVM lvcreate -L $DISK_SIZE -n $DISK_NAME $DISK_VG terom@7: terom@7: ## SELinux? terom@7: #$SEMANAGE fcontext -a -t virt_image_t $DISK terom@7: #$RESTORECON -v $DISK terom@7: terom@7: ## virt-install terom@7: cmd_confirm $VIRT_INSTALL \ terom@7: --connect $LIBVIRT \ terom@7: --name $GUEST_NAME \ terom@7: --ram $(expand_MB $GUEST_RAM) --vcpus $GUEST_VCPUS \ terom@7: --cdrom "$INSTALL_ISO" \ terom@7: --os-variant $GUEST_OS_VARIANT \ terom@14: --disk path=$DISK_PATH,bus=$GUEST_DISK_BUS \ terom@7: --network bridge:$NET_BRIDGE \ terom@7: --virt-type $LIBVIRT_TYPE \ terom@16: --vnc \ terom@7: --serial pty terom@7: fi terom@7: