#!/bin/bash
# Author: Bram Borggreve ( borggreve @ gmail dot com )
# Homepage: http://code.google.com/p/createvm/
# License: GPL, see: http://www.gnu.org/copyleft/gpl.txt

### Todo ###
# - Automatically register the VM with vmware server
# - Add ESX support

### Some default variables ###

# Program log_info
PROGRAM_NAME=$(basename $0)
PROGRAM_TITLE="Create VMware Virtual Machines in bash"
PROGRAM_VER="0.5"
PROGRAM_COPYRIGHT="Copyright 2007-2008. \
Distributed under GPL V2 license. No warranty whatsoever, express or implied."
PROGRAM="$PROGRAM_NAME $PROGRAM_VER"
LOGFILE=createvm.log
BINARIES=(gzip tar vmware-vdiskmanager zip)
BINARY_TESTS=yes

# Default settings
DEFAULT_QUIET=no        # Don't ask for confirmations, only when critical
DEFAULT_YES=no          # Yes to al questions (warning: will overwrite existing files) 
DEFAULT_ZIPIT=no        # Create .zip archive
DEFAULT_TARGZIT=no      # Create .tar.gz archive 
DEFAULT_START_VM=no     # Start VM after creating it
DEFAULT_WRKPATH=.       # Location where output will be

# Default VM parameters
VM_CONF_VER=8           # VM Config version
VM_VMHW_VER=4           # VM Hardware version
VM_RAM=256              # Default RAM
VM_NVRAM=nvram          # Default bios file
VM_ETH_TYPE=bridged     # Default network type
VM_MAC_ADDR=default     # Default MAC address
VM_DISK_SIZE=8          # Default DISK size (GB's)
VM_DISK_TYPE=SCSI       # Default DISK type
VM_USE_USB=FALSE        # Enable USB
VM_USE_SND=FALSE        # Enable sound
VM_USE_CDD=FALSE        # Enable CD drive
VM_USE_ISO=FALSE        # Enable and load ISO 
VM_USE_FDD=FALSE        # Enable and load FDD

# This is the list of supported OS'es
SUPPORT_OS=(winVista longhorn winNetBusiness winNetEnterprise winNetStandard \
winNetWeb winXPPro winXPHome win2000AdvServ win2000Serv win2000Pro winNT winMe \
win98 win95 win31 windows winVista-64 longhorn-64 winNetEnterprise-64 \
winNetStandard-64 winXPPro-64 ubuntu redhat rhel4 rhel3 rhel2 suse sles \
mandrake nld9 sjds turbolinux other26xlinux other24xlinux linux ubuntu-64 \
rhel4-64 rhel3-64 sles-64 suse-64 other26xlinux-64 other24xlinux-64 other-64 \
otherlinux-64 solaris10-64 solaris10 solaris9 solaris8 solaris7 solaris6 \
solaris netware6 netware5 netware4 netware freeBSD-64 freeBSD darwin other)

# Some color codes
COL_EMR="\033[1;31m"    # Bold red
COL_EMG="\033[1;32m"    # Bold green
COL_EMW="\033[1;37m"    # Bold white
COL_RESET="\033[0;00m"  # Default colors

### Main functions ###

# Show version log_info
function version() {
    echo -e "${COL_EMW}$PROGRAM - $PROGRAM_TITLE${COL_RESET}"
    echo -e $PROGRAM_COPYRIGHT
}
# Print status message
function log_status() {
    echo -ne "    $1 "
}
# Print if cmd returned oke or failed
function check_status() {
    if [[ $? -ne 0 ]] ; then
        echo -e "${COL_EMR}[FAILED]${COL_RESET}"
        exit 1;
    else
        echo -e "${COL_EMG}[OK]${COL_RESET}"
    fi
}
# Print normal message
function log_message() {
    echo -e "    $1 "
}
# Print highlighted message
function log_info() {
    echo -e "${COL_EMW}    $1${COL_RESET} "
}

function _log_alert() {
    local _type=$1
    shift;
    echo -e "${COL_EMW}[$_type] ${COL_RESET}${COL_EMR}$1${COL_RESET} "
}

# Print log_alert log_message
function log_alert() {
    _log_alert '!' "$@"
}

# Print log_error log_message
function log_error() {
    _log_alert 'E' "$@"
}

function _ask_user() {
    local msg=""
    if [ "$1" = "y" ] ; then
        msg="${COL_EMG}[Yn]"
    elif [ "$1" = "n" ] ; then
        msg="${COL_EMR}[yN]"
    fi
    echo -ne "${COL_EMW}[?] Is it oke to continue?     $msg $COL_RESET"
    read YESNO
    [ -z $YESNO ] && YESNO=$1
    YESNO=$(echo $YESNO | tr A-Z a-z)
    if [ "$YESNO" = "n" ] || [ "$YESNO" = "no" ]  ; then log_alert "Stopped..."; exit 0; fi
    # If it's not yes
    [ "$YESNO" = "y" ] || [ "$YESNO" = "yes" ] && return
    _ask_user $1
}

# Ask if a user wants to continue, default to YES
function ask_oke(){
    [ ! "$DEFAULT_QUIET" = "yes" ] && _ask_user y
}

# Ask if a user wants to continue, default to NO
function ask_no_oke(){
    [ ! "$DEFAULT_YES" = "yes" ] && _ask_user n
}

### Specific funtions ###

# Print Help message
function usage() {
    echo -e "${COL_EMW}$PROGRAM - $PROGRAM_TITLE${COL_RESET}
Usage: $PROGRAM_NAME GuestOS OPTIONS

VM Options:
 -n, --name [NAME]              Friendly name of the VM       (default: <os-type>-vm)
 -r, --ram [SIZE]               RAM size in MB                (default: $VM_RAM)
 -d, --disk-size [SIZE]         HDD size in GB                (default: $VM_DISK_SIZE)
 -t, --disk-type [TYPE]         HDD Interface, SCSI or IDE    (default: $VM_DISK_TYPE)
 -e, --eth-type [TYPE]          Network Type (bridge/nat/etc) (default: $VM_ETH_TYPE)
 -m, --mac-addr [ADDR]          Use static mac address        (address: 00:50:56:xx:xx:xx)

 -c, --cdrom                    Enable CDROM Drive            (default: $VM_USE_CDD)
 -i, --iso [FILE]               Enable CDROM Iso              (default: $VM_USE_ISO)
 -f, --floppy                   Enable Floppy Drive           (default: $VM_USE_FDD)
 -a, --audio                    Enable sound card             (default: $VM_USE_SND)
 -u, --usb                      Enable USB                    (default: $VM_USE_USB)
 -b, --bios [PATH]              Path to custom bios file      (default: $VM_NVRAM)

Program Options:
 -x [COMMAND]                   Start the VM with this command 

 -w, --working-dir [PATH]       Path to use as Working Dir    (default: current working dir)
 -z, --zip                      Create .zip from this VM
 -g, --tar-gz                   Create .tar.gz from this VM

 -l, --list                     Generate a list of VMware Guest OS'es
 -q, --quiet                    Runs without asking questions, accept the default values
 -y, --yes                      Say YES to all questions. This overwrites existing files!! 
 -B, --binary                   Disable the check on binaries
 -M, --monochrome               Don't use colors
 
 -h, --help                     This help screen
 -v, --version                  Shows version information
 -ex, --sample                  Show some examples 

Dependencies:
This program needs the following binaries in its path: ${BINARIES[@]}"
}

# Show some examples
function print_examples(){
    echo -e "${COL_EMW}$PROGRAM - $PROGRAM_TITLE${COL_RESET}
Here are some examples:

 Create an Ubuntu Linux machine with a 20GB hard disk and a different name
   $ $PROGRAM_NAME ubuntu -d 20 -n \"My Ubuntu VM\" 

 Silently create a SUSE Linux machine with 512MB ram, a fixed MAC address and zip it
   $ $PROGRAM_NAME suse -r 512 -q -m 00:50:56:01:25:00 -z 

 Create a Windows XP machine with 512MB and audio, USB and CD enabled
   $ $PROGRAM_NAME winXPPro -r 512 -a -u -c 

 Create an Ubuntu VM with 512MB and open and run it in vmware
   $ $PROGRAM_NAME ubuntu -r 512 -x \"vmware -x\""

}
    
function _summary_item() {
    local item=$1
    shift;
    printf "    %-26s" "$item"
    echo -e "${COL_EMW} $* ${COL_RESET}"
}

# Print a summary with some of the options on the screen
function show_summary(){
    log_info "I am about to create this Virtual Machine:"
    _summary_item "Guest OS" $VM_OS_TYPE
    _summary_item "Display name" $VM_NAME
    _summary_item "RAM (MB)" $VM_RAM
    _summary_item "HDD (Gb)" $VM_DISK_SIZE
    _summary_item "HDD interface" $VM_DISK_TYPE
    _summary_item "BIOS file" $VM_NVRAM
    _summary_item "Ethernet type" $VM_ETH_TYPE
    _summary_item "Mac address" $VM_MAC_ADDR
    _summary_item "Floppy disk" $VM_USE_FDD
    _summary_item "CD/DVD drive" $VM_USE_CDD
    _summary_item "CD/DVD image" $VM_USE_ISO
    _summary_item "USB device" $VM_USE_USB
    _summary_item "Sound Card" $VM_USE_SND
    ask_oke
}

function add_config_param() {
    if [ -n "$1" ] ; then
        local item=$1
        shift;
        [ -n "$1" ] && CONFIG_PARAM="$CONFIG_PARAM\n$item = \"$@\""
        return
    fi
    # if empty, then reset the config params
    CONFIG_PARAM=""
}

function print_config() {
    echo -e $CONFIG_PARAM > "$VM_VMX_FILE"
}

# Create the .vmx file
function create_conf(){
    log_status "Creating config file...   "

    add_config_param config.version $VM_CONF_VER
    add_config_param virtualHW.version $VM_VMHW_VER
    add_config_param displayName $VM_NAME
    add_config_param guestOS $VM_OS_TYPE
    add_config_param memsize $VM_RAM

    if [ ! $VM_NVRAM = "nvram" ]; then
        FILENAME=$(basename $VM_NVRAM)
        cp $VM_NVRAM "$WORKING_DIR/$FILENAME"
        add_config_param nvram $FILENAME
    else
        add_config_param nvram $VM_NVRAM
    fi

    add_config_param ethernet0.present TRUE
    add_config_param ethernet0.connectionType $VM_ETH_TYPE

    if [ ! $VM_MAC_ADDR = "default" ]; then
        add_config_param ethernet0.addressType static
        add_config_param ethernet0.address $VM_MAC_ADDR
    else
        add_config_param ethernet0.addressType generated
    fi

    if [ ! $VM_DISK_TYPE = "IDE" ]; then
        add_config_param scsi0:0.present TRUE
        add_config_param scsi0:0.fileName $VM_DISK_NAME
    else 
        add_config_param ide0:0.present TRUE
        add_config_param ide0:0.fileName $VM_DISK_NAME
    fi

    if [ ! $VM_USE_USB = "FALSE" ]; then
        add_config_param usb.present TRUE
        add_config_param usb.generic.autoconnect FALSE
    fi

    if [ ! $VM_USE_SND = "FALSE" ]; then
        add_config_param sound.present TRUE
        add_config_param sound.fileName -1
        add_config_param sound.autodetect TRUE
        add_config_param sound.startConnected FALSE
    fi

    if [ ! $VM_USE_FDD = "FALSE" ]; then
        add_config_param floppy0.present TRUE
        add_config_param floppy0.startConnected FALSE
    else
        add_config_param floppy0.present FALSE
    fi

    if [ ! $VM_USE_CDD = "FALSE" ]; then
        add_config_param ide0:1.present TRUE
        add_config_param ide0:1.fileName auto detect
        add_config_param ide0:1.autodetect TRUE
        add_config_param ide0:1.deviceType cdrom-raw
        add_config_param ide0:1.startConnected FALSE
    fi

    if [ ! $VM_USE_ISO = "FALSE" ]; then
        add_config_param ide1:0.present TRUE
        add_config_param ide1:0.fileName $VM_USE_ISO
        add_config_param ide1:0.deviceType cdrom-image
        add_config_param ide1:0.startConnected TRUE
        add_config_param ide1:0.mode persistent
    fi

    add_config_param annotation "This VM is created by $PROGRAM"

    print_config
    check_status
}

# Create the working dir
function create_working_dir(){
    log_info "Creating Virtual Machine..."
    log_status "Creating working dir...   "
    mkdir -p "$WORKING_DIR" 1> /dev/null
    check_status
}
# Create the virtual disk
function create_virtual_disk(){
    log_status "Creating virtual disk...  "
    local adapter=buslogic
    [ "$VM_DISK_TYPE" = "IDE" ] && adapter=ide
    vmware-vdiskmanager -c -a $adapter -t 1 -s $VM_DISK_SIZE "$WORKING_DIR/$VM_DISK_NAME" &> $LOGFILE
    check_status
}
# Generate a zip or tar.gz archive
function create_archive(){
    if [ "$DEFAULT_ZIPIT" = "yes" ]; then
        # Generate zipfile
        log_status "Generate zip file...      "
        cd "$DEFAULT_WRKPATH"
        zip -q -r "$VM_OUTP_FILE_ZIP" "$VM_NAME" 1> /dev/null
        check_status
    fi
    if [ "$DEFAULT_TARGZIT" = "yes" ]; then
        # Generate tar.gz file
        log_status "Generate tar.gz file...   "
        cd "$DEFAULT_WRKPATH"
        tar cvzf "$VM_OUTP_FILE_TAR" "$VM_NAME" 1> /dev/null
        check_status
    fi
}
# Print OS list.
function list_guest_os() {
    echo "List of Guest Operating Systems:"

    local max="${#SUPPORT_OS[@]}"
    for ((i=0;i < max; i=i+3)) ; do
        printf "%-25s %-25s %-25s\n" ${SUPPORT_OS[$i]} ${SUPPORT_OS[$((i + 1))]} ${SUPPORT_OS[$((i + 2))]}
    done
}
# Check if selected OS is in the OS list
function run_os_test(){
    local OS
    for OS in ${SUPPORT_OS[@]} ; do 
        # Everything OK, no need to continue
        [ $OS = "$VM_OS_TYPE" ] && return
    done
    log_error "Guest OS \"$VM_OS_TYPE\" is unknown..."
    log_message "Run \"$PROGRAM_NAME -l\" for a list of Guest OS'es..."
    log_message "Run \"$PROGRAM_NAME -h\" for help..."
    log_message "Run \"$PROGRAM_NAME -ex\" for examples..."
    exit 1
}
# Check for binaries and existance of previously created VM's
function run_tests(){
    # Check for needed binaries
    if [ "$BINARY_TESTS" = "yes" ]; then
        log_info "Checking binaries..."
        local app
        for app in ${BINARIES[@]} ; do
            log_status ""
            printf " - %-22s " "$app..."
            which $app 1> /dev/null
            check_status
        done
    fi
    # Check if working dir file exists
    log_info "Checking files and directories..."
    if [ -e "$WORKING_DIR" ]; then
        log_alert "Working dir already exists, i will trash it!"
        ask_no_oke
        log_status "Trashing working dir...   "
        rm -rf "$WORKING_DIR" 1>/dev/null
        check_status
    fi
    # Check if zip file exists
    if [ "$DEFAULT_ZIPIT" = "yes" ]; then
        if [ -e "$VM_OUTP_FILE_ZIP" ]; then 
            log_alert "zip file already exists, i will trash it!"
            ask_no_oke
            log_status "Trashing zip file...      "
            rm "$VM_OUTP_FILE_ZIP" 1>/dev/null
            check_status
        fi
    fi
    # Check if tar.gz file exists
    if [ "$DEFAULT_TARGZIT" = "yes" ]; then
        if [ -e "$VM_OUTP_FILE_TAR" ]; then 
            log_alert "tar.gz file already exists, i will trash it!"
            ask_no_oke
            log_status "Trashing tar.gz file...   "
            rm "$VM_OUTP_FILE_TAR" 1>/dev/null
            check_status
        fi
    fi
}
# Clean up working dir
function clean_up(){
    # Back to base dir...
    cd - &> /dev/null
    # Clean up if zipped or tar-gzipped, and announce file location
    if [ "$DEFAULT_ZIPIT" = "yes" ]; then 
        CLEANUP='yes'
        VMLOCATION="$VM_OUTP_FILE_ZIP $VMLOCATION"
    fi
    if [ "$DEFAULT_TARGZIT" = "yes" ]; then 
        CLEANUP='yes'
        VMLOCATION="$VM_OUTP_FILE_TAR $VMLOCATION"
    fi
    if [ "$CLEANUP" = "yes" ]; then
        log_status "Cleaning up workingdir... "
        rm -rf "$WORKING_DIR"
        check_status
    else
        VMLOCATION="$VM_VMX_FILE"
    fi
    log_info "Grab you VM here: $VMLOCATION"
}
# Start VM if asked for 
function start_vm(){
    if [ "$DEFAULT_START_VM" = "yes" ]; then 
        log_info "Starting Virtual Machine..."
        $VMW_BIN $VM_VMX_FILE
    fi
}

### The flow! ###

# Chatch some parameters if the first one is not the OS.
if [ "$1" = "" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then usage; exit; fi
if [ "$1" = "-v" ] || [ "$1" = "--version" ];     then version; exit; fi
if [ "$1" = "-l" ] || [ "$1" = "--list" ];     then list_guest_os; exit 1; fi
if [ "$1" = "-ex" ] || [ "$1" = "--sample" ];     then print_examples; exit 1; fi

# The first parameter is the Guest OS Type
VM_OS_TYPE="$1"

# Set default VM Name
VM_NAME="$VM_OS_TYPE-vm"

# Run OS test
run_os_test

# Shift through all parameters to search for options
shift
while [ "$1" != "" ]; do
    case $1 in
    -a | --audio )
        VM_USE_SND="TRUE"
    ;;
    -b | --bios )
        shift
        VM_NVRAM=$1
    ;;
    -B | --binary )
        BINARY_TESTS=no
    ;;
    -c | --cdrom )
        VM_USE_CDD="TRUE"
    ;;
    -d | --disk-size )
        shift
        VM_DISK_SIZE=$1
    ;;
    -e | --eth-type )
        shift
        VM_ETH_TYPE=$1
    ;;
    -f | --floppy )
        VM_USE_FDD="TRUE"
    ;;
    -g | --tar-gz )
        DEFAULT_TARGZIT="yes"
    ;;
    -i | --iso )
        shift
        VM_USE_ISO=$1
    ;;
    -m | --mac-addr )
        shift
        VM_MAC_ADDR=$1
    ;;
    -M | --monochrome )
        COL_EMR=""
        COL_EMG=""
        COL_EMW=""
        COL_RESET=""
    ;;
    -n | --name )
        shift
        VM_NAME="$1"
    ;;
    -r | --ram )
        shift
        VM_RAM=$1
    ;;
    -t | --disk-type )
        shift
        VM_DISK_TYPE=$1
    ;;
    -u | --usb )
        VM_USE_USB="TRUE"
    ;;
    -q | --quiet )
        DEFAULT_QUIET="yes"
    ;;
    -v | --version )
        version
    ;;
    -w | --working-dir )
        shift
        DEFAULT_WRKPATH=$1
    ;;
    -x  )
        shift
        DEFAULT_START_VM="yes"
        VMW_BIN="$1"
    ;;
    -y | --yes )
        DEFAULT_QUIET="yes"
        DEFAULT_YES="yes"
    ;;
    -z | --zip )
        DEFAULT_ZIPIT="yes"
    ;;
    * )
        log_error "Euhm... what do you mean by \"$*\"?"
        log_message "Run \"$PROGRAM_NAME -h\" for help"
        log_message "Run \"$PROGRAM_NAME -ex\" for examples..."
        exit 1
    esac
    shift
done

# Set the names of the output files
VM_OUTP_FILE_ZIP="$VM_NAME.zip"
VM_OUTP_FILE_TAR="$VM_NAME.tar.gz"

# The last parameters are set
WORKING_DIR="$DEFAULT_WRKPATH/$VM_NAME"
VM_VMX_FILE="$WORKING_DIR/$VM_OS_TYPE.vmx"
VM_DISK_NAME="$VM_DISK_TYPE-$VM_OS_TYPE.vmdk"
VM_DISK_SIZE="$VM_DISK_SIZE""Gb"

# Print banner
version
# Display summary
show_summary
# Do some tests
run_tests

# Create working environment
create_working_dir
# Write config file
create_conf
# Create virtual disk
create_virtual_disk
# Create archine
create_archive

# Clean up environment
clean_up
# Run the VM
start_vm

### The End! ###
