Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 411 lines (365 sloc) 15.6 KB
#!/usr/bin/env zsh
# Set stop on error / enable debug
set -euo pipefail
#set -vx
############################################################################
# DEVBOOK TEST VM FUNCTIONS
############################################################################
##{{{#######################################################################
############################################################################
# FUNCTIONS
############################################################################
# Clean Upon Exit
cleanup() {
OP=${OP-""}
if [[ "$OP" == "build" && "$?" != "0" ]]; then
echo "${C_HIL}Devbook build process ended. Would you like to ssh into the VM? (y|n)${C_RES}"
read -t 5 INPUT
if [[ "$INPUT" == "y" ]]; then
ssh -t "$DEVBOOK_VM_SSH_HOST"
fi
fi
exit 0
}
if [[ "${(%):-%x}" == "$0" ]]; then
trap cleanup EXIT
fi
# Print a string line wrapped in "===" headers
printline() {
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' =
printf "%s\n" "$1"
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' =
}
# Logging functions
readonly LOG_FILE="/tmp/$(basename "$0").log"
info() { echo "[INFO] $*" | tee -a "$LOG_FILE" >&2 ; }
warning() { echo "[WARNING] $*" | tee -a "$LOG_FILE" >&2 ; }
error() { echo "[ERROR] $*" | tee -a "$LOG_FILE" >&2 ; }
fatal() { echo "[FATAL] $*" | tee -a "$LOG_FILE" >&2 ; exit 1 ; }
# Accept Message & Error Code
quit() {
if [[ -z $1 ]]; then MESSAGE="An error has occurred"; else MESSAGE=$1; fi
if [[ -z $2 ]]; then ERROR_CODE=1; else ERROR_CODE=$2; fi
echo "$MESSAGE" 1>&2; exit "$ERROR_CODE";
}
# Retrieve an Ansible var in playbook.
ansible_var() {
if [[ ! -z "$1" ]]; then
VAR="$1"
echo $(ansible-playbook main.yml -i inventory --tags "get-var" --extra-vars "var_name=$VAR" | grep "$VAR" | sed -e 's/[[:space:]]*"\([^"]*\)"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\2/g' | sed -e "s/\"$VAR\": null//g" | sed -e "s/VARIABLE IS NOT DEFINED!//g" )
fi
}
############################################################################
# VARS
############################################################################
# Output colors.
C_HIL="\033[36m"
C_WAR="\033[33m"
C_SUC="\033[32m"
C_ERR="\033[31m"
C_RES="\033[0m"
# Set defaults
DEVBOOK_CONFIG_VARS=("DEVBOOK_VM_NAME" "DEVBOOK_VM_SSH_HOST" "DEVBOOK_VM_SSH_USER" "DEVBOOK_VM_DIR" "DEVBOOK_VM_BACKUP_DIR" "DEVBOOK_REMOTE_CONFIG")
DEVBOOK_CONFIG_FILENAME=".devbook.env"
if [[ -f "$HOME/$DEVBOOK_CONFIG_FILENAME" ]]; then
source "$HOME/$DEVBOOK_CONFIG_FILENAME"
fi
typeset -A DEVBOOK_CONFIG_DEFAULTS
# The VBox guest VM name
DEVBOOK_CONFIG_DEFAULTS[DEVBOOK_VM_NAME]="macOS"
# The VBox guest VM host name
DEVBOOK_CONFIG_DEFAULTS[DEVBOOK_VM_SSH_HOST]="macos"
# The VBox guest VM user name
DEVBOOK_CONFIG_DEFAULTS[DEVBOOK_VM_SSH_USER]="admin"
# The home-relative path to VBox VMs
DEVBOOK_CONFIG_DEFAULTS[DEVBOOK_VM_DIR]="Documents/VirtualBox"
# The absolute local dir to backup VM to
DEVBOOK_CONFIG_DEFAULTS[DEVBOOK_VM_BACKUP_DIR]=""
# The URL of the config file used to config devboook
DEVBOOK_CONFIG_DEFAULTS[DEVBOOK_REMOTE_CONFIG]=""
# Snapshots / Build Code
typeset -A DEVBOOK_VM_SNAPSHOTS
typeset -A DEVBOOK_VM_B_SCRIPTS
DEVBOOK_VM_SNAPSHOTS[Init]="An initialized macOS High Sierra install with a configured admin user."
DEVBOOK_VM_SNAPSHOTS[Ansibled]="A macOS High Sierra install with ansible & XCode CLI tools installed."
DEVBOOK_VM_B_SCRIPTS[Ansibled]="ssh -t \"$DEVBOOK_VM_SSH_HOST\" \"time sh <(curl -sL https://jig.io/devbook-boot)\""
DEVBOOK_VM_SNAPSHOTS[SIPOff]="A macOS High Sierra install ready for DevBook w/ SIP Off."
DEVBOOK_VM_B_SCRIPTS[SIPOff]="sleep 0"
DEVBOOK_VM_SNAPSHOTS[Packaged]="A macOS High Sierra install with DevBook installed."
DEVBOOK_VM_B_SCRIPTS[Packaged]="ssh -t \"$DEVBOOK_VM_SSH_HOST\" \"PATH="/usr/local/bin:\$PATH"; time sh <(curl -sL https://jig.io/devbook-init) \""
##}}}#######################################################################
#/ Usage: $SCRIPT OP [ARGS]
#/
#/ <OP>: [config|control|snapshot|image|backup|time-sync|build]
#/ <ARGS>: On OP:
#/
#/ config: Config devbook-test-vm app.
#/
#/ control COMMAND: Control the VM:
#/ <COMMAND>: [on|state|manage|off]
#/ <ARGS>:
#/ on: Turn on the VM.
#/ state [poweroff|savestate|pause|resume]: Set the VM to specified state.
#/ manage: Open Virtualbox App.
#/ off: Turn off the VM.
#/
#/ snapshot COMMAND: Control VM snapshot:
#/ <COMMAND>: [take|delete|restore|list]
#/ <ARGS>:
#/ take NAME: Take a snapshot named NAME.
#/ delete NAME INDEX: Delete the INDEX-th snapshot named NAME.
#/ restore NAME: Restore the snapshot named NAME.
#/ list: List all VM snapshots.
#/
#/ image:
#/ <COMMAND>: [mount|umount]
#/ <ARGS>:
#/ mount IMAGE: Mount the IMAGE iso into the VM.
#/ umount IMAGE: Unmount the IMAGE name out of the VM.
#/
#/ backup: Backup the VM dir to $DEVBOOK_VM_BACKUP_DIR.
#/
#/ time-sync: Sync the VM clock to the current time.
#/
#/ test-tag TAG Test the devbook TAG in the VM.
#/
#/ sync-files REMOTE_PATH [LOCAL_PATH]: Sync files between local and VM.
#/ <REMOTE_PATH>: The remote path to sync to.
#/ <LOCAL_PATH>: The local path to sync from (defaults to cwd)
#/
#/ build SNAPSHOT CONFIG_URL: Build the VM SNAPSHOT.
#/ <SNAPSHOT>: [Ansibled|SIPOff|Packaged] The pre-set VM snapshot name.
#/ <CONFIG_URL>: The Devbook config URL.
#/
#/ Examples:
#/ Options:
#/ --help: Display this help message
SCRIPT=$(basename "$0")
SCRIPT_PATH=$0:A
usage() { grep '^#/' "$SCRIPT_PATH" | cut -c4- | sed -e 's/\$SCRIPT/'"$SCRIPT"'/g' ; exit 0 ; }
expr "$*" : ".*--help" > /dev/null && usage
############################################################################
# MAIN
############################################################################
# Handle options
# Add options x: - required arg
while getopts 'h' FLAG; do
case "${FLAG}" in
h) usage; exit 1 ;;
*) : ;;
esac
done
# Verify client
if (! which VBoxManage > /dev/null 2>&1 ); then
echo "${C_ERR}VirtualBox/VBoxManage is needed. Run ${C_WAR}brew cask install virtualbox${C_RES}${C_ERR} to install.${C_RES}"
exit 1
else
# Look for VM and register.
if [[ ! $(VBoxManage list vms | grep "\"$DEVBOOK_VM_NAME\"") ]]; then
echo "${C_HIL}Could not find the VM ${C_WAR}$DEVBOOK_VM_NAME${C_RES}${C_HIL}. Adding VMs in ${C_WAR}$HOME/$DEVBOOK_VM_DIR${C_RES}${C_HIL}${C_RES}"
for VM in $(find "$HOME/$DEVBOOK_VM_DIR" -maxdepth 2 -type f -name "*.vbox"); do
VBoxManage registervm "$VM" 2> /dev/null || true
done
fi
# Retrieve & route command
OP=${1-""}
case $OP in
# DEFAULT DEVBOOK VM CONFIG
config)
# Show current config
if [[ -f "$HOME/$DEVBOOK_CONFIG_FILENAME" ]]; then
echo "${C_WAR}CURRENT CONFIG: ${C_RES}"
cat "$HOME/$DEVBOOK_CONFIG_FILENAME"
echo ""
fi
# Add new config
CONFIG_VARS=""
for CONFIG in "${DEVBOOK_CONFIG_VARS[@]}"
do
echo "${C_SUC}Enter value for ${C_WAR}$CONFIG${C_RES}${C_SUC}:${C_RES}"
read INPUT
CONFIG_VARS="$CONFIG_VARS$CONFIG=$INPUT\n"
echo ""
done
echo "$CONFIG_VARS"| head -n$(expr $(echo "$CONFIG_VARS" | wc -l | xargs) - 1) > "$HOME/$DEVBOOK_CONFIG_FILENAME"
echo "${C_SUC}Config saved to ${C_WAR}$HOME/$DEVBOOK_CONFIG_FILENAME${C_RES}${C_RES}"
;;
# STARTING/CONTROL DEVBOOK VM
control)
COMMAND=${2-""}
case $COMMAND in
on)
VBoxManage startvm "$DEVBOOK_VM_NAME" --type gui
;;
state)
STATE=${3-""}
VBoxManage controlvm "$DEVBOOK_VM_NAME" $STATE
;;
manage)
open -a "VirtualBox"
;;
off)
osascript -e 'quit app "VirtualBox"'
VBoxManage controlvm "$DEVBOOK_VM_NAME" savestate
osascript -e 'quit app "VirtualBox"'
;;
*) usage;;
esac
;;
# SNAPSHOT DEVBOOK VM
snapshot)
COMMAND=${2-""}
SNAPSHOT=${3-""}
SNAPSHOT_ITEM=${4-""}
SNAPSHOT_DESC=${DEVBOOK_VM_SNAPSHOTS[$SNAPSHOT]-}
if [[ "$COMMAND" != "list" && -z "$SNAPSHOT_DESC" ]]; then
echo "${C_ERR}${C_WAR}$SNAPSHOT${C_RES}${C_ERR} is not a valid snapshot.${C_RES}"
exit 1
fi
if [[ "$SNAPSHOT_ITEM" != "" ]]; then
UUID=$(VBoxManage snapshot "$DEVBOOK_VM_NAME" list | grep "$SNAPSHOT" | sed -e 's/\ *Name\ *:\ *[[:alpha:]]*\ (UUID:\ \(.*\)).*/\1/g' | sed -n "$SNAPSHOT_ITEM"p)
else
UUID=$(VBoxManage snapshot "$DEVBOOK_VM_NAME" list | grep "$SNAPSHOT" | sed -e 's/\ *Name\ *:\ *[[:alpha:]]*\ (UUID:\ \(.*\)).*/\1/g')
fi
case $COMMAND in
take)
VBoxManage snapshot "$DEVBOOK_VM_NAME" take "$SNAPSHOT" --description "$SNAPSHOT_DESC"
;;
delete)
#VBoxManage snapshot "$DEVBOOK_VM_NAME" delete "$SNAPSHOT"
VBoxManage snapshot "$DEVBOOK_VM_NAME" delete "$UUID"
;;
restore)
VBoxManage controlvm "$DEVBOOK_VM_NAME" poweroff || true
VBoxManage snapshot "$DEVBOOK_VM_NAME" restore "$SNAPSHOT"
echo "${C_HIL}Waiting for snapshot restoration...${C_RES}"
sleep 5
VBoxManage startvm "$DEVBOOK_VM_NAME" --type gui || true
;;
list)
VBoxManage snapshot "$DEVBOOK_VM_NAME" list
;;
*) usage;;
esac
;;
# IMAGE-MOUNT DEVBOOK
image)
COMMAND=${2-""}
IMAGE=${3-""}
if [[ -z "$IMAGE" ]]; then
echo "${C_ERR}Need an iso to mount or the image name (to unmount).${C_RES}"
exit 1
fi
case $COMMAND in
mount)
VBoxManage storageattach "$DEVBOOK_VM_NAME" --storagectl "SATA" --port 1 --device 0 --type dvddrive --medium "$IMAGE"
;;
umount)
ssh -t "$DEVBOOK_VM_SSH_HOST" "diskutil eject \"$IMAGE\""
VBoxManage storageattach "$DEVBOOK_VM_NAME" --storagectl "SATA" --port 1 --device 0 --type dvddrive --medium "emptydrive"
;;
*) usage;;
esac
;;
### Make ISO
# hdiutil makehybrid -o ~/Downloads/Devbook-Test.iso ~/Downloads/Devbook -iso -joliet
### Local manage
# hdiutil attach ~/Downloads/Devbook.dmg
# diskutil eject Devbook
# SYNC FILES WITH DEVBOOK
sync-files)
DEVBOOK_VM_SSH_PATH=${2-""}
LOCAL_PATH=${3-"."}
rsync -r -a -v -e ssh --delete "$LOCAL_PATH" $DEVBOOK_VM_SSH_USER@$DEVBOOK_VM_SSH_HOST:$DEVBOOK_VM_SSH_PATH
;;
# BACKUP DEVBOOK
backup)
if [[ -d "${DEVBOOK_VM_BACKUP_DIR:?}/${DEVBOOK_VM_NAME:?}" ]]; then
rm -rf "${DEVBOOK_VM_BACKUP_DIR:?}/${DEVBOOK_VM_NAME:?}"
fi
if [[ -d "$HOME/${DEVBOOK_VM_DIR:?}/${DEVBOOK_VM_NAME:?}" ]]; then
cp -R "$HOME/${DEVBOOK_VM_DIR:?}/${DEVBOOK_VM_NAME:?}" "${DEVBOOK_VM_BACKUP_DIR:?}"
echo "${C_HIL}${C_WAR}$HOME/${DEVBOOK_VM_DIR:?}/${DEVBOOK_VM_NAME:?}${C_RES}${C_HIL} backed up.${C_RES}"
else
echo "${C_ERR}Could not find ${C_WAR}$HOME/${DEVBOOK_VM_DIR:?}/${DEVBOOK_VM_NAME:?}${C_RES}${C_ERR}.${C_RES}"
fi
;;
# RESYNC DEVBOOK CLOCK
time-sync)
ssh -t "$DEVBOOK_VM_SSH_HOST" "sudo ntpdate -u time.apple.com"
;;
# TEST TAG
test-tag)
# Confirm arg
TAG=${2-""}
if [[ -z "$TAG" ]]; then
echo "${C_ERR}Need a tag to test.${C_RES}"
exit 1
fi
# Setup Devbook code
VM_TMP=$(ssh "$DEVBOOK_VM_SSH_HOST" "echo \$TMPDIR" | xargs)
ssh "$DEVBOOK_VM_SSH_HOST" "if [[ ! -d \"\$TMPDIR/devbook\" ]]; then mkdir \"\$TMPDIR/devbook\"; fi"
if [[ -f "init.sh" ]]; then
echo ""
echo "${C_HIL}Copying local Devbook code...${C_RES}"
scp -qr . "$DEVBOOK_VM_SSH_HOST:${VM_TMP}devbook" 2> /dev/null || true
fi
# Get all tags minus always
echo ""
echo "${C_HIL}Executing Devbook code...${C_RES}"
ssh -t "$DEVBOOK_VM_SSH_HOST" "
PATH=\"/usr/local/bin:\$PATH\"
export ANSIBLE_DEPRECATION_WARNINGS=0
cd \"${VM_TMP}devbook\"
ansible-galaxy install -r requirements.yml
ansible-playbook --list-tags main.yml -i inventory | grep 'TASK TAGS' | sed -e 's/.*TASK TAGS: \[\(.*\)\]/\1/g' | sed -e ' s/always[,| ]*//g' | sed -e ' s/ //g' | tr ',' '\n' | grep -v \"$TAG\" 2> /dev/null > .devbook.skip
for DEVBOOK in \$(find \"\$HOME/.devbook/\" -maxdepth 2 -type f -path \"\$HOME/.devbook/*/main.yml\")
do
DEVBOOK_DIR=\$(dirname \$DEVBOOK)
ansible-playbook --list-tags \"\$DEVBOOK\" -i inventory | grep 'TASK TAGS' | sed -e 's/.*TASK TAGS: \[\(.*\)\]/\1/g' | sed -e ' s/always[,| ]*//g' | sed -e ' s/ //g' | tr ',' '\n' 2> /dev/null > \"\$DEVBOOK_DIR/.devbook.skip\"
done
./init.sh
rm .devbook.skip
for DEVBOOK in \$(find \"\$HOME/.devbook/\" -maxdepth 2 -type f -path \"\$HOME/.devbook/*/main.yml\")
do
DEVBOOK_DIR=\$(dirname \$DEVBOOK)
rm \"\$DEVBOOK_DIR/.devbook.skip\"
done
"
;;
# BUILD DEVBOOK
build)
SNAPSHOT=${2-""}
SNAPSHOT_SCRIPT=${DEVBOOK_VM_B_SCRIPTS[$SNAPSHOT]-}
if [[ -z "$SNAPSHOT" || -z "$SNAPSHOT_SCRIPT" ]]; then
echo "${C_ERR}Need a valid snapshot to build.${C_RES}"
exit 1
fi
# Send instructions for SIPOff testing
if [[ "$SNAPSHOT" == "SIPOff" ]]; then
VBoxManage controlvm "$DEVBOOK_VM_NAME" poweroff || true
echo "${C_HIL}Running build for ${C_WAR}$SNAPSHOT${C_RES}${C_HIL}.${C_RES}"
echo "${C_WAR}In order to disable SIP, do the following: .${C_RES}"
echo "${C_SUC} 1. Press Enter to restart VM.${C_RES}"
echo "${C_SUC} 2. ${C_WAR}PRESS SPACE/F12 QUICKLY WHEN VM SCREEN APPEARS${C_RES}${C_SUC} -> VirtualBox EFI BIOS${C_RES}"
echo "${C_SUC} 3. Boot Manager > EFI Internal Shell${C_RES}"
echo "${C_SUC} 4. Enter FS2:${C_RES}"
echo "${C_SUC} 5. cd com.apple.recovery.boot${C_RES}"
echo "${C_SUC} 6. boot.efi${C_RES}"
echo "${C_SUC} 7. Goto: Utilities > Terminal${C_RES}"
echo "${C_SUC} 8. csrutil disable|enable; reboot;${C_RES}"
echo "${C_SUC} 9. devbook-test-vm snapshot take \"$SNAPSHOT\"${C_RES}"
read INPUT
open -a "VirtualBox"
VBoxManage startvm "$DEVBOOK_VM_NAME" --type gui
fi
# Run Devbook w/ config
if [[ "$SNAPSHOT" == "Packaged" ]]; then
DEVBOOK_CONFIG=${3-""}
SNAPSHOT_SCRIPT="ssh -t \"$DEVBOOK_VM_SSH_HOST\" \"PATH="/usr/local/bin:\$PATH"; time sh <(curl -sL https://jig.io/devbook-init) $DEVBOOK_CONFIG\""
fi
eval "$SNAPSHOT_SCRIPT"
;;
*) usage;;
esac
fi