Proxmox automated conversion to template

This post is not finished, more text will be provided soon.

The following script takes a virtual machine with the purpose to act as a base machine for new machines and creates a Proxmox template from it by:

  • Creating a snapshot
  • Deleting prevously created template if it exists
  • Cloning the machine
  • Connecting to the machine using temporary ssh key
  • Using previously published oem-config-prepare script on the cloned machine (with the added removal of the temporary ssh key)
  • Converting the cloned virtual machine to a template

Most configurable options are located in the configurations section

#!/usr/bin/env bash
#
# Date: 2017-05-31
# Version: 1.0
# Author: Stellan Nordenbro <stellan.nordenbro@gmail.com>
#
# The MIT License (MIT)
#
# Copyright (c) 2017 Stellan Nordenbro
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

# Quick-check before we allow bad things to happen
if [ -z "${BASH_VERSINFO}" ]; then
  echo "ERROR: You must execute this script with BASH"
  exit 255
fi

# Go to the correct folder
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR

# The usage text
usage() {
  echo "Usage: `basename $0` [-hdqS]" 1>&2
  echo
  echo "Optional parameters:" 1>&2
  echo " -h             Displays this message" 1>&2
  echo " -d             Dry-run: no commands will be executed" 1>&2
  echo " -q             Quiet: output to stdout will be hidden" 1>&2
  echo " -S             Silent: output to both stdout and stderr will be hidden" 1>&2
  echo
  exit 1;
}

# Parse commandline
ALLARGS="$@"
set -- $(getopt hdqS -- "$@")
while true;
do
    case "$1" in
                (-h) usage;;
                (-d) DRY_RUN=yes;;
                (-q) QUIET=yes;;
                (-S) SILENT=yes;;
                (--) ;;
                (-*) echo "Error: unrecognized option $1" 1>&2; exit 1;;
                (*)  break;;
    esac
    shift
done

# send all stdout to /dev/null
if [ "${QUIET}" = "yes" ] || [ "${SILENT}" = "yes" ]; then
        exec 1> /dev/null
fi

# send all stdout and stderr to /dev/null
if [ "${SILENT}" = "yes" ]; then
        exec 2> /dev/null
fi

##############################################
# FUNCTIONS
#############################################
function GetVMOption() {
	if [ $# -ne 2 ]; then
                echo "ERROR: ${FUNCNAME} expected 2 parameters: Usage ${FUNCNAME} VM_ID OPTION"
                exit 1
	fi

	value=$(qm config $1 | grep $2 | awk '{ print $2}')
	echo "$value"
}

function SetVMOption() {
	if [ $# -ne 3 ]; then
		echo "ERROR: ${FUNCNAME} expected 2 parameters: Usage ${FUNCNAME} VM_ID OPTION VALUE"
		exit 1
	fi

	if [ "${DRY_RUN}" = "yes" ]; then
		echo "   DRY-RUN:   qm set $1 -${2} $3"
	else
		qm set $1 -${2} $3
	fi
}

#############################################
# CONFIGURATION
#############################################
HOSTNAME=$(hostname)
VM_ID=10000
VM_NAME=$(GetVMOption ${VM_ID} name)
SNAPSHOT_NAME="A$(date +"%Y%m%d_%H%M")"
SNAPSHOT_DESCRIPTION="Automated snapshot by template creation script"
TEMPLATE_ID=10001
TEMPLATE_NAME="template-${VM_NAME}"
TEMPLATE_POOL="Templates"
TEMPLATE_STORAGE="shared-templates"
TEMPLATE_DESCRIPTION="Template created $(date +"%Y-%m-%d %T")"
SSH_KEY="~/.ssh/temp_id_rsa"

echo "Creating template of base virtual machines on $HOSTNAME"
echo -e "-------------------------------------------------------nn"

#############################################
# SNAPSHOT
#############################################
echo "* Creating snapshot ${SNAPSHOT_NAME} on virtual machine ${VM_NAME} with id ${VM_ID}..."
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm snapshot ${VM_ID} "${SNAPSHOT_NAME}" -description "${SNAPSHOT_DESCRIPTION}" -vmstate 0"
else
	qm snapshot ${VM_ID} "${SNAPSHOT_NAME}" -description "${SNAPSHOT_DESCRIPTION}" -vmstate 0
	snapshot_count=$(qm listsnapshot ${VM_ID} | awk '{print $1}' | grep "${SNAPSHOT_NAME}" | wc -l)
	if [ $snapshot_count != 1 ]; then
		echo "ERROR: Snaphot creation failed on virtual machine ${VM_NAME} with id ${VM_ID}"
		exit 1
	fi
fi
echo -e "* Done creating snapshot ${SNAPSHOT_NAME}.nn"

#############################################
# EXISTING TEMPLATE CHECK
#############################################
echo "* Checking for existing template vms..."
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm list | grep -v "VMID" | grep "${TEMPLATE_NAME}" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l"
fi
echo -e "* Done checking for existing tempalte vms.nn"

#############################################
# EXISTING TEMPLATE DELETE
#############################################
vm_count=$(qm list | grep -v "VMID" | grep "${TEMPLATE_NAME}" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l)
vm_id_count=$(qm list | grep -v "VMID" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l)
if [ $vm_id_count != $vm_count ]; then
	echo "ERROR: There is a virtual machine with id ${TEMPLATE_ID} but probably not then name ${TEMPLATE_NAME}, aborting." | tee /dev/stderr
	exit 1
fi

if [ $vm_count = 1 ]; then
	echo "* Deleting old template ${TEMPLATE_NAME} with id ${TEMPLATE_ID} after clearing protection"
	if [ "${DRY_RUN}" = "yes" ]; then
		SetVMOption ${TEMPLATE_ID} protection 0
		echo "   DRY-RUN:   qm destroy ${TEMPLATE_ID}"
	else
		SetVMOption ${TEMPLATE_ID} protection 0
		qm destroy ${TEMPLATE_ID}
		if [ $(qm list | grep -v "VMID" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l) = 1 ]; then
			echo "ERROR: Unable to delete VM ID: ${TEMPLATE_ID}, aborting." | tee /dev/stderr
			exit 1
		fi
	fi
	echo -e "* Done deleting existing template.nn"
fi

#############################################
# CLONE VM TO TEMPLATE
#############################################
echo "* Creating template clone ${TEMPLATE_NAME} from snapshot ${SNAPSHOT_NAME}"
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm clone ${VM_ID} ${TEMPLATE_ID} -description "${TEMPLATE_DESCRIPTION}" -format qcow2 -full 1 -name "${TEMPLATE_NAME}" -pool "${TEMPLATE_POOL}" -snapname "${SNAPSHOT_NAME}" -storage "${TEMPLATE_STORAGE}""
else
	qm clone ${VM_ID} ${TEMPLATE_ID} -description "${TEMPLATE_DESCRIPTION}" -format qcow2 -full 1 -name "${TEMPLATE_NAME}" -pool "${TEMPLATE_POOL}" -snapname "${SNAPSHOT_NAME}" -storage "${TEMPLATE_STORAGE}"
	sleep 10
	if [ $(qm list | grep -v "VMID" | grep "${TEMPLATE_NAME}" | awk '{print $1}' | grep ${TEMPLATE_ID} | wc -l) != 1 ]; then
		echo "ERROR: Unable to clone VM ID: ${VM_ID} to TEMPLATE ID: ${TEMPLATE_ID}, aborting." | tee /dev/stderr
		exit 1
	fi
fi
echo -e "* Done creating template clone ${TEMPLATE_NAME}.nn"

#############################################
# TEMPLATE PREPARATION
#############################################
echo "* Preparing template ${TEMPLATE_NAME}"
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm start ${TEMPLATE_ID}"
	echo "   DRY_RUN:   sleep 30"
	echo "   DRY-RUN:   ip=$(qm agent ${TEMPLATE_ID} network-get-interfaces | grep ip-address | grep -o "192.168.[0-9]*.[0-9]*" | head -n 1)"
	echo "   DRY_RUN:   ssh -4 -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -i ~/.ssh/base_id_rsa root@${ip} "/sbin/oem-config-prepare -f""
else
	qm start ${TEMPLATE_ID}
	sleep 30
	ip=$(qm agent ${TEMPLATE_ID} network-get-interfaces | grep ip-address | grep -o "192.168.[0-9]*.[0-9]*" | head -n 1)
	count=0
	while [[ -z $ip ]] && [[ $count -lt 600 ]]
	do
		count=$((count+1))
		echo -n "."
		sleep 1
		ip=$(qm agent ${TEMPLATE_ID} network-get-interfaces | grep ip-address | grep -o "192.168.[0-9]*.[0-9]*" | head -n 1)
	done

	if [ -z $ip ]; then
		echo "ERROR: The machine does not seem to have an IP, maybe boot failed, aborting." | tee /dev/stderr
		exit 1
	fi

	ssh -4 -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -i ${SSH_KEY} root@${ip} "/sbin/oem-config-prepare -f"
fi
echo -e "* Done preparing template ${TEMPLATE_NAME}.nn"

#############################################
# CONVERT TEMPLATE
#############################################
echo "* Converting virtual machine ${TEMPLATE_NAME} to template"
if [ "${DRY_RUN}" = "yes" ]; then
	echo "   DRY-RUN:   qm wait ${TEMPLATE_ID}"
	echo "   DRY-RUN:   qm template ${TEMPLATE_ID}"
else
	qm wait ${TEMPLATE_ID}
	qm template ${TEMPLATE_ID}
fi
echo -e "* Done converting virtual machine ${TEMPLATE_NAME} to template.nn"

echo "Script execution done."

If anyone else have use for this script then go ahead, no guarantees of course, use it on your own risk…

Advertisements

Add space tiles to macOS dock

The dock in macOS is a convenient way to access commonly used application, the problem is that it can be hard to locate the application if there are many dock items.

To make it a little bit easier spaces can be added to the dock, so items can be grouped.

The spaces in the dock are added using a Terminal command, so do a spotlight search for terminal and then run the following command as many times as needed, a new space tile is added each time the command is executed.

defaults write com.apple.dock persistent-apps -array-add '{tile-data={}; tile-type="spacer-tile";}'; killall Dock

The spaces can be reordered as any other docked item.

This is what it looks like

BEFORE

Screen Shot 2017-05-14 at 13.10.22

AFTER

Screen Shot 2017-05-14 at 13.10.53