461 lines
14 KiB
Bash
461 lines
14 KiB
Bash
# This file contains bash functions that are used by osbash on the host.
|
|
|
|
source "$LIB_DIR/functions.sh"
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Conditional execution
|
|
#-------------------------------------------------------------------------------
|
|
# TODO: Create a help function and display it under help by default or with
|
|
# option --help (-h).
|
|
# exec_cmd is used for conditional execution:
|
|
#
|
|
# OSBASH=exec_cmd
|
|
#
|
|
# Execute command only if OSBASH is set:
|
|
# ${OSBASH:-:} cmd args
|
|
#
|
|
# Execute command only if OSBASH is not set:
|
|
# ${OSBASH:+:} cmd args
|
|
#
|
|
# Disable actual call to VBoxManage (selectively override configuration):
|
|
# OSBASH= cmd args
|
|
#
|
|
# Enable call to VBoxManage (selectively override configuration):
|
|
# OSBASH=exec_cmd cmd args
|
|
|
|
function exec_cmd {
|
|
local cmd=$1
|
|
shift
|
|
$cmd "$@"
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
function get_base_disk_name {
|
|
echo "base-$VM_ACCESS-$DISTRO.vdi"
|
|
}
|
|
|
|
function get_base_disk_path {
|
|
echo "$DISK_DIR/$(get_base_disk_name)"
|
|
}
|
|
|
|
# From DISTRO string (e.g., ubuntu-14.04-server-amd64), get first component
|
|
function get_distro_name {
|
|
# Match up to first dash
|
|
local re='([^-]*)'
|
|
|
|
if [[ $DISTRO =~ $re ]]; then
|
|
echo "${BASH_REMATCH[1]}"
|
|
fi
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# ssh
|
|
#-------------------------------------------------------------------------------
|
|
|
|
# Check permission for osbash insecure private key
|
|
function check_osbash_private_key {
|
|
local key_name="osbash_key"
|
|
local osbash_key_dir=$LIB_DIR/osbash-ssh-keys
|
|
local osbash_key_path=$osbash_key_dir/$key_name
|
|
|
|
if ! ls -l "$osbash_key_path"|grep -q "^-r--------"; then
|
|
echo "Adjusting permissions for $osbash_key_path"
|
|
chmod 400 "$osbash_key_path"
|
|
fi
|
|
}
|
|
|
|
function strip_top_dir {
|
|
local full_path=$1
|
|
echo "${full_path/$TOP_DIR\//}"
|
|
}
|
|
|
|
# Copy files or directories to VM (incl. implied directories; HOME is TOP_DIR)
|
|
function vm_scp_to_vm {
|
|
local ssh_port=$1
|
|
shift
|
|
|
|
check_osbash_private_key
|
|
|
|
while (($#)); do
|
|
local src_path=$1
|
|
shift
|
|
local target_path=$(strip_top_dir "$src_path")
|
|
local target_dir=$(dirname "$target_path")
|
|
vm_ssh "$ssh_port" "mkdir -p $target_dir"
|
|
scp -q -r \
|
|
-i "$LIB_DIR/osbash-ssh-keys/osbash_key" \
|
|
-o "UserKnownHostsFile /dev/null" \
|
|
-o "StrictHostKeyChecking no" \
|
|
-P "$ssh_port" \
|
|
"$src_path" "$VM_SHELL_USER@localhost:$target_path"
|
|
done
|
|
}
|
|
|
|
# Execute commands via ssh
|
|
function vm_ssh {
|
|
local ssh_port=$1
|
|
shift
|
|
|
|
check_osbash_private_key
|
|
|
|
# Some operating systems (e.g., Mac OS X) export locale settings to the
|
|
# target that cause some Python clients to fail. Override with a standard
|
|
# setting (LC_ALL=C).
|
|
LC_ALL=C ssh -q \
|
|
-i "$LIB_DIR/osbash-ssh-keys/osbash_key" \
|
|
-o "UserKnownHostsFile /dev/null" \
|
|
-o "StrictHostKeyChecking no" \
|
|
-p "$ssh_port" \
|
|
"$VM_SHELL_USER@localhost" "$@"
|
|
}
|
|
|
|
function wait_for_ssh {
|
|
local ssh_port=$1
|
|
|
|
echo -e -n "${CStatus:-}Waiting for ssh server to respond on local port ${CData:-}$ssh_port.${CReset:-}"
|
|
while [ : ]; do
|
|
if vm_ssh "$ssh_port" exit ; then
|
|
break
|
|
else
|
|
echo -n .
|
|
sleep 1
|
|
fi
|
|
done
|
|
echo
|
|
}
|
|
|
|
# Copy one script to VM and execute it via ssh; log output to separate file
|
|
function ssh_exec_script {
|
|
local ssh_port=$1
|
|
local script_path=$2
|
|
|
|
vm_scp_to_vm "$ssh_port" "$script_path"
|
|
|
|
local remote_path=$(strip_top_dir "$script_path")
|
|
|
|
echo -en "\n$(date) start $remote_path"
|
|
|
|
local script_name="$(basename "$script_path" .sh)"
|
|
local prefix=$(get_next_prefix "$LOG_DIR" "auto")
|
|
local log_path=$LOG_DIR/${prefix}_${script_name}.auto
|
|
|
|
local rc=0
|
|
vm_ssh "$ssh_port" "bash $remote_path && rm -vf $remote_path" \
|
|
> "$log_path" 2>&1 || rc=$?
|
|
if [ $rc -ne 0 ]; then
|
|
echo >&2
|
|
echo -e "${CError:-}ERROR: ssh returned status ${CData:-}$rc${CError:-} for${CData:-} $remote_path${CReset:-}" |
|
|
tee >&2 -a "$LOG_DIR/error.log"
|
|
# kill osbash host scripts
|
|
kill -- -$$
|
|
fi
|
|
|
|
echo -en "\n$(date) done"
|
|
}
|
|
|
|
# Wait for sshd, prepare autostart dirs, and execute autostart scripts on VM
|
|
function ssh_process_autostart {
|
|
# Run this function in sub-shell to protect our caller's environment
|
|
# (which might be _our_ enviroment if we get called again)
|
|
(
|
|
|
|
source "$CONFIG_DIR/config.$vm_name"
|
|
|
|
local ssh_port=$VM_SSH_PORT
|
|
|
|
wait_for_ssh "$ssh_port"
|
|
vm_ssh "$ssh_port" "rm -rf lib config autostart"
|
|
vm_scp_to_vm "$ssh_port" "$TOP_DIR/lib" "$TOP_DIR/config"
|
|
|
|
local script_path=""
|
|
for script_path in "$AUTOSTART_DIR/"*.sh; do
|
|
ssh_exec_script "$ssh_port" "$script_path"
|
|
rm -f "$script_path" >&2
|
|
done
|
|
touch "$STATUS_DIR/done"
|
|
|
|
)
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Autostart mechanism
|
|
#-------------------------------------------------------------------------------
|
|
|
|
function autostart_reset {
|
|
clean_dir "$AUTOSTART_DIR"
|
|
clean_dir "$STATUS_DIR"
|
|
}
|
|
|
|
function process_begin_files {
|
|
local processing=("$STATUS_DIR"/*.sh.begin)
|
|
if [ -n "${processing[0]-}" ]; then
|
|
local file
|
|
for file in "${processing[@]}"; do
|
|
echo >&2 -en "\nVM processing $(basename "$file" .begin)"
|
|
rm "$file"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Wait until all autofiles are processed (indicated by a "$STATUS_DIR/done"
|
|
# file created either by osbashauto or ssh_process_autostart)
|
|
function wait_for_autofiles {
|
|
shopt -s nullglob
|
|
|
|
${WBATCH:-:} wbatch_wait_auto
|
|
# Remove autostart files and return if we are just faking it for wbatch
|
|
${OSBASH:+:} autostart_reset
|
|
${OSBASH:+:} return 0
|
|
|
|
until [ -f "$STATUS_DIR/done" -o -f "$STATUS_DIR/error" ]; do
|
|
# Note: begin files (created by indicate_current_auto) are only visible
|
|
# if the STATUS_DIR directory is shared between host and VM
|
|
${WBATCH:-:} process_begin_files
|
|
echo >&2 -n .
|
|
sleep 1
|
|
done
|
|
# Check for remaining *.sh.begin files
|
|
${WBATCH:-:} process_begin_files
|
|
if [ -f "$STATUS_DIR/done" ]; then
|
|
rm "$STATUS_DIR/done"
|
|
else
|
|
echo -e >&2 "${CError:-}\nERROR occured. Exiting.${CReset:-}"
|
|
kill -- -$$
|
|
fi
|
|
echo
|
|
}
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
# Prepending numbers ensures scripts will be executed in the order they
|
|
# were added to the queue.
|
|
|
|
function _autostart_queue {
|
|
local src_path=$SCRIPTS_DIR/$1
|
|
local src_name=${1##*/}
|
|
|
|
# If we get a target name, file will be renamed
|
|
local target_name=${2:-$src_name}
|
|
|
|
if [[ $target_name = *.sh ]]; then
|
|
# Create target file name like 01_apt_init.sh
|
|
local prefix=$(get_next_prefix "$AUTOSTART_DIR" "sh" 2)
|
|
target_name="${prefix}_$target_name"
|
|
fi
|
|
|
|
if [ "$src_name" = "$target_name" ]; then
|
|
echo >&2 -e "\t$src_name"
|
|
else
|
|
echo >&2 -e "\t$src_name -> $target_name"
|
|
fi
|
|
|
|
cp -- "$src_path" "$AUTOSTART_DIR/$target_name"
|
|
${WBATCH:-:} wbatch_cp_auto "$src_path" "$AUTOSTART_DIR/$target_name"
|
|
}
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
# Print to the console which file requested guest scripts to run
|
|
function log_autostart_source {
|
|
# If the caller doesn't provide a config file, log the caller's source file
|
|
local src_file=${1:-${BASH_SOURCE[1]##*/}}
|
|
echo >&2 "Copying autostart files set in $src_file"
|
|
}
|
|
|
|
# autostart <src_dir> <file> <new_name>
|
|
# e.g. autostart osbash init_xxx_node.sh init_controller_node.sh
|
|
function autostart_and_rename {
|
|
local src_dir=$1
|
|
local src_file=$2
|
|
local target_file=$3
|
|
|
|
# Don't log this file -- log our caller's source file
|
|
log_autostart_source "${BASH_SOURCE[1]##*/}"
|
|
|
|
_autostart_queue "$src_dir/$src_file" "$target_file"
|
|
}
|
|
|
|
# autostart <file> [<file> ...]
|
|
# e.g. autostart zero_empty.sh osbash/base_fixups.sh
|
|
function autostart {
|
|
# Don't log this file -- log our caller's source file
|
|
log_autostart_source "${BASH_SOURCE[1]##*/}"
|
|
|
|
while (($#)); do
|
|
local src_file=$1
|
|
shift
|
|
_autostart_queue "$src_file"
|
|
done
|
|
}
|
|
|
|
# Parse options given to configuration commands. Return parsed values by
|
|
# setting variables to be used by caller.
|
|
function get_cmd_options {
|
|
local OPTIND
|
|
local opt
|
|
|
|
while getopts :g:n: opt; do
|
|
case $opt in
|
|
g)
|
|
vm_ui=$OPTARG
|
|
;;
|
|
n)
|
|
vm_name=$OPTARG
|
|
;;
|
|
*)
|
|
echo -e >&2 "${CError:-}Error: bad option ${CData:-}$OPTARG.${CReset:-}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
shift $((OPTIND-1))
|
|
|
|
# Assign the remaining arguments back to args
|
|
args=$@
|
|
}
|
|
|
|
# Parse command and arguments after a "cmd" token in config/scripts.*
|
|
function command_from_config {
|
|
local cmd=$1
|
|
shift
|
|
|
|
# Local variables that may be changed by get_cmd_options
|
|
local vm_name=${NODE_NAME:-""}
|
|
local vm_ui=${VM_UI:-""}
|
|
|
|
local args=$@
|
|
case "$cmd" in
|
|
boot)
|
|
# Format: boot [-g <gui_type>] [-n <node_name>]
|
|
# Boot with queued autostart files now, wait for end of scripts
|
|
# processing
|
|
get_cmd_options $args
|
|
echo >&2 "VM_UI=$vm_ui _vbox_boot_with_autostart $vm_name"
|
|
VM_UI=$vm_ui _vbox_boot_with_autostart "$vm_name"
|
|
;;
|
|
snapshot)
|
|
# Format: snapshot [-n <node_name>] <snapshot_name>
|
|
get_cmd_options $args
|
|
local shot_name=$args
|
|
echo >&2 vm_snapshot "$vm_name" "$shot_name"
|
|
vm_snapshot "$vm_name" "$shot_name"
|
|
;;
|
|
wait_for_shutdown)
|
|
# Format: wait_for_shutdown [-n <node_name>]
|
|
get_cmd_options $args
|
|
echo >&2 vm_wait_for_shutdown "$vm_name"
|
|
vm_wait_for_shutdown "$vm_name"
|
|
;;
|
|
snapshot_cycle)
|
|
# Format: snapshot_cycle [-g <gui_type>] [-n <node_name>]
|
|
# comprises shutdown, boot, wait_for_shutdown, snapshot
|
|
get_cmd_options $args
|
|
local shot_name=$args
|
|
echo >&2 snapshot_cycle "$vm_name" "$shot_name"
|
|
_autostart_queue "osbash/shutdown.sh"
|
|
_vbox_boot_with_autostart "$vm_name"
|
|
vm_wait_for_shutdown "$vm_name"
|
|
vm_snapshot "$vm_name" "$shot_name"
|
|
;;
|
|
init_node)
|
|
# Format: init_node [-n <node_name>]
|
|
get_cmd_options $args
|
|
echo >&2 vm_init_node "$vm_name"
|
|
vm_init_node "$vm_name"
|
|
;;
|
|
queue)
|
|
# Queue a script for autostart
|
|
# Format: queue <script_name>
|
|
local script_rel_path=$args
|
|
echo >&2 _autostart_queue "$script_rel_path"
|
|
_autostart_queue "$script_rel_path"
|
|
;;
|
|
*)
|
|
echo -e >&2 "${CError:-}Error: invalid cmd: ${CData:-}$cmd${CReset:-}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Parse config/scripts.* configuration files
|
|
function autostart_from_config {
|
|
local config_file=$1
|
|
local config_path=$CONFIG_DIR/$config_file
|
|
|
|
if [ ! -f "$config_path" ]; then
|
|
echo -e >&2 "${CMissing:-}Config file not found: ${CData:-}$config_file${CReset:-}"
|
|
return 1
|
|
fi
|
|
|
|
log_autostart_source "$config_file"
|
|
|
|
# Open file on file descriptor 3 so programs we call in this loop (ssh)
|
|
# are free to mess with the standard file descriptors.
|
|
exec 3< "$config_path"
|
|
while read -r field_1 field_2 <&3; do
|
|
if [[ $field_1 =~ (^$|^#) ]]; then
|
|
# Skip empty lines and lines that are commented out
|
|
continue
|
|
elif [ "$field_1" == "cmd" ]; then
|
|
if [ -n "${JUMP_SNAPSHOT:-""}" ]; then
|
|
if [[ $field_2 =~ ^snapshot.*${JUMP_SNAPSHOT} ]]; then
|
|
echo >&2 "Skipped forward to snapshot $JUMP_SNAPSHOT."
|
|
unset JUMP_SNAPSHOT
|
|
fi
|
|
else
|
|
command_from_config $field_2
|
|
fi
|
|
else
|
|
# Syntax error
|
|
echo -e -n >&2 "${CError:-}ERROR in ${CInfo:-}$config_file: ${CData:-}'$field_1${CReset:-}"
|
|
if [ -n "$field_2" ]; then
|
|
echo >&2 " $field_2'"
|
|
else
|
|
echo >&2 "'"
|
|
fi
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Functions to get install ISO images
|
|
#-------------------------------------------------------------------------------
|
|
|
|
function download {
|
|
local url=$1
|
|
local dest_dir=$2
|
|
local dest_file=$3
|
|
local rc=0
|
|
|
|
local wget_exe=$(which wget)
|
|
mkdir -pv "$dest_dir"
|
|
if [ -n "$wget_exe" ]; then
|
|
$wget_exe --output-document "$dest_dir/$dest_file" "$url"||rc=$?
|
|
else
|
|
# Mac OS X has curl instead of wget
|
|
local curl_exe=$(which curl)
|
|
if [ -n "$curl_exe" ]; then
|
|
$curl_exe "$url" -o "$dest_dir/$dest_file"||rc=$?
|
|
fi
|
|
fi
|
|
if [ $rc -ne 0 ]; then
|
|
echo -e >&2 "${CError:-}Unable to download ${CData:-}$url${CError:-}, quitting.${CReset:-}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
function get_iso_name {
|
|
basename "${ISO_URL:-}"
|
|
}
|
|
|
|
function find_install-iso {
|
|
local iso_name=$1
|
|
if [ ! -f "$ISO_DIR/$iso_name" ]; then
|
|
echo >&2 "$iso_name not in $ISO_DIR; downloading"
|
|
download "$ISO_URL" "$ISO_DIR" "$iso_name"
|
|
fi
|
|
}
|
|
|
|
# vim: set ai ts=4 sw=4 et ft=sh:
|