320 lines
11 KiB
Bash
Executable File
320 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
# vim: filetype=sh shiftwidth=4 softtabstop=4 expandtab
|
|
#
|
|
# Copyright (c) 2023 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# IPMI serial console capture script.
|
|
# Captures the serial console output via underlying 'ipmitool' tool.
|
|
# Also acts as an interactive ipmitool wrapper, capturing all console input/output.
|
|
#
|
|
set -o nounset; # Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR. Same as 'set -u'
|
|
set -o pipefail; # Catch the error in case a piped command fails
|
|
set +o posix # Ensure posix mode is off. Required for process substitution
|
|
# set -o xtrace; # Turn on traces, useful while debugging (short form: on: 'set -x' off: 'set +x')
|
|
|
|
################################################################################
|
|
# Helpers
|
|
|
|
# shellcheck disable=SC2155
|
|
readonly SCRIPTNAME=$(basename "$0")
|
|
# shellcheck disable=SC2155,SC2034
|
|
readonly SCRIPTDIR=$(readlink -m "$(dirname "$0")")
|
|
|
|
BMC_USER=${BMC_USER:-}
|
|
BMC_ADDRESS=${BMC_ADDRESS:-}
|
|
BMC_PASSWORD=${BMC_PASSWORD:-}
|
|
LOG_FILE=${LOG_FILE:-$(pwd)/${SCRIPTNAME%.*}-$(date '+%Y%m%d-%H%M').log}
|
|
SOL_ACTIVE=
|
|
|
|
DEBUG=
|
|
|
|
help() {
|
|
cat<<EOF
|
|
Wrapper script to capture the output of 'ipmitool' in a log file.
|
|
|
|
This script starts up ipmitool for the given BMC session parameters, capturing all output into a log file.
|
|
|
|
Once connected, you can interact with the session normally.
|
|
|
|
To stop the capture you either exit the ipmitool session normally (via the
|
|
hotkey: '<Enter>~.'), or you can invoke the script again using the --kill
|
|
option to cleanly shut everything down.
|
|
|
|
There are two intended use-cases for this script:
|
|
1. Automatically capturing the serial console output during install via ipmitool
|
|
In this case, the --redirect flag is provided, which simply redirects all output
|
|
to the given log file. The console is not meant to be interactive in this case
|
|
2. Provide an interactive wrapper around ipmitool which captures all input and output
|
|
into the provided log file.
|
|
|
|
Note on log file: The log file is captured including raw control characters. It
|
|
is best viewed using 'less' with the '-R/--RAW-CONTROL-CHARS' option.
|
|
|
|
USAGE:
|
|
$SCRIPTNAME [-H <bmc-address] [-U <bmc-user>] [ -P <bmc-password> ] [--force-deactivate]
|
|
|
|
ARGUMENTS:
|
|
IPMI connection arguments: If these are not given you will be prompted.
|
|
You can also provide any of these as environment variables if preferred.
|
|
|
|
-H|--host <bmc-address> : BMC host address [env: BMC_ADDRESS]
|
|
-U|--user <bmc-user> : BMC user [env: BMC_USER]
|
|
-P|--password <bmc-address> : BMC password [env: BMC_PASSWORD]
|
|
|
|
-l|--log <file path> : Path to the console capture log file (directory must exist)
|
|
Default: ./${SCRIPTNAME}-YYYY-MM-DD-HHMM.log
|
|
-k|--kill : Stop the capture, cleanup the ipmitool session for this BMC address
|
|
-f|--force-deactivate : Issue a 'sol deactivate' command before connecting
|
|
-r|--redirect : Redirect all ouput to file (do not show on local console)
|
|
This option is only useful for automated/background capture.
|
|
|
|
-h|--help: print this help
|
|
|
|
EXAMPLES:
|
|
|
|
Capture:
|
|
$SCRIPTNAME -H 2620:10a:a001:df0::2 -U sysadmin -P sysadmin
|
|
$SCRIPTNAME # you will be prompted for BMC details
|
|
|
|
# You can provide any of BMC_ADDRESS, BMC_USER, BMC_PASSWORD via environment variables:
|
|
export BMC_PASSWORD=SecretPassword
|
|
$SCRIPTNAME -H 2620:10a:a001:df0::2 -U sysadmin
|
|
|
|
# Override log file
|
|
$SCRIPTNAME -l ./subcloud1-console-\$(date '+%Y%m%d-%H%M').log
|
|
|
|
Shutdown:
|
|
# Inside the ipmitool session: <Enter>~.
|
|
|
|
# Or force sol deactivate and kill ipmitool for this configuration:
|
|
$SCRIPTNAME -H 2620:10a:a001:df0::2 -U sysadmin -P sysadmin --kill
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
# Logging: these all log to stderr
|
|
die() { >&2 colorecho red "FATAL: $*"; exit 1; }
|
|
die_with_rc() { local rc=$1; shift; >&2 colorecho red "FATAL: $*, rc=$rc"; exit "$rc"; }
|
|
check_rc_die() { local rc=$1; shift; [ "$rc" != "0" ] && die_with_rc "$rc" "$@"; return 0; }
|
|
check_rc_err() { local rc=$1; shift; [ "$rc" != "0" ] && log_error "$*, rc=$rc"; return 0; }
|
|
log_error() { >&2 colorecho red "ERROR: $*"; }
|
|
log_warn() { >&2 colorecho orange "WARN: $*"; }
|
|
log_info() { >&2 echo "$*"; }
|
|
log_debug() { if [ -n "$DEBUG" ]; then >&2 echo "DEBUG: $*"; fi; }
|
|
log_progress() { >&2 colorecho green "$*"; }
|
|
get_logdate() { date '+%Y-%m-%d %H:%M:%S'; } # eg: log_info "$(get_logdate) My log message"
|
|
|
|
_init_log() {
|
|
LOG_FILE="${LOG_FILE:-$(pwd)/${SCRIPTNAME%.*}.log}"
|
|
log_debug "$(get_logdate) Logging output to $LOG_FILE"
|
|
}
|
|
|
|
redirect_output_to_file() {
|
|
# output to file only:
|
|
_init_log
|
|
if [ -f "${LOG_FILE}" ]; then
|
|
# append
|
|
exec &>> "${LOG_FILE}"
|
|
else
|
|
exec &> "${LOG_FILE}"
|
|
fi
|
|
}
|
|
|
|
tee_output_to_file() {
|
|
# output to console and file:
|
|
_init_log
|
|
exec &> >(exec tee --append "${LOG_FILE}")
|
|
}
|
|
|
|
colorecho() { # usage: colorecho <colour> <text> or colorecho -n <colour> <text>
|
|
local echo_arg=
|
|
if [ "$1" = "-n" ]; then
|
|
echo_arg="-n"; shift;
|
|
fi
|
|
local colour="$1"; shift
|
|
case "${colour}" in
|
|
red) echo ${echo_arg} -e "$(tput setaf 1)$*$(tput sgr0)"; ;;
|
|
green) echo ${echo_arg} -e "$(tput setaf 2)$*$(tput sgr0)"; ;;
|
|
green-bold) echo ${echo_arg} -e "$(tput setaf 2; tput bold)$*$(tput sgr0)"; ;;
|
|
yellow) echo ${echo_arg} -e "$(tput setaf 3; tput bold)$*$(tput sgr0)"; ;;
|
|
orange) echo ${echo_arg} -e "$(tput setaf 3)$*$(tput sgr0)"; ;;
|
|
blue) echo ${echo_arg} -e "$(tput setaf 4)$*$(tput sgr0)"; ;;
|
|
purple) echo ${echo_arg} -e "$(tput setaf 5)$*$(tput sgr0)"; ;;
|
|
cyan) echo ${echo_arg} -e "$(tput setaf 6)$*$(tput sgr0)"; ;;
|
|
bold) echo ${echo_arg} -e "$(tput bold)$*$(tput sgr0)"; ;;
|
|
normal|*) echo ${echo_arg} -e "$*"; ;;
|
|
esac
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# Script Functions
|
|
|
|
ipmitool_deactivate() {
|
|
# Forcefully deactivate via sol deactivate
|
|
# Use -E to supply password via IPMI_PASSWORD (security)
|
|
export IPMI_PASSWORD=${BMC_PASSWORD}
|
|
log_info "Disconnecting: ipmitool -I lanplus -H ${BMC_ADDRESS} -U ${BMC_USER} -E sol deactivate"
|
|
ipmitool -I lanplus -H "${BMC_ADDRESS}" -U "${BMC_USER}" -E sol deactivate
|
|
rc=$?
|
|
log_info "Exit code from sol deactivate: ${rc}"
|
|
export SOL_ACTIVE=
|
|
}
|
|
|
|
ipmitool_activate() {
|
|
# Establish remote console access via ipmi sol
|
|
# Use -E to supply password via IPMI_PASSWORD
|
|
export IPMI_PASSWORD=${BMC_PASSWORD}
|
|
export SOL_ACTIVE=1
|
|
log_info "Connecting: ipmitool -I lanplus -H ${BMC_ADDRESS} -U ${BMC_USER} -E sol activate"
|
|
ipmitool -I lanplus -H "${BMC_ADDRESS}" -U "${BMC_USER}" -E sol activate
|
|
local -i rc=$?
|
|
# We see exit code of 143 when killed
|
|
if [ ${rc} -eq 0 ] || [ ${rc} -eq 143 ]; then
|
|
log_progress "ipmitool sol activate normal exit"
|
|
else
|
|
log_error "ipmitool sol activate abnormal exit [rc: ${rc}]"
|
|
exit ${rc}
|
|
fi
|
|
export SOL_ACTIVE=
|
|
}
|
|
|
|
ipmitool_kill() {
|
|
log_progress "Shutting down IPMI capture"
|
|
ipmitool_deactivate
|
|
local pid pid2
|
|
# Now kill ipmitool for this BMC address
|
|
pid=$(pgrep -a ipmitool | awk '/'"${BMC_ADDRESS}"'/ { print $1; }')
|
|
if [ -n "${pid}" ]; then
|
|
log_info "Killing ipmitool pid: ${pid}"
|
|
kill "${pid}"
|
|
sleep 1
|
|
pid2=$(pgrep -a ipmitool | awk '/'"${BMC_ADDRESS}"'/ { print $1; }')
|
|
if [ -n "${pid2}" ]; then
|
|
log_error "FAILED to kill ipmitool pid: ${pid}, pid2: ${pid2} (retrying with -9)"
|
|
kill -9 "${pid2}"
|
|
return 1
|
|
fi
|
|
log_info "Finished. You may need to run 'reset' in the original terminal."
|
|
else
|
|
log_info "No ipmitool session found for ${BMC_ADDRESS}"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
do_cleanup() {
|
|
if [ -n "${SOL_ACTIVE}" ]; then
|
|
ipmitool_deactivate
|
|
fi
|
|
}
|
|
|
|
|
|
################################################################################
|
|
# Main
|
|
#
|
|
main() {
|
|
local arg_force_deactivate=
|
|
local arg_redirect_to_file=
|
|
local arg_rvmc_config=
|
|
local arg_kill=
|
|
while [ $# -gt 0 ] ; do
|
|
case "${1:-""}" in
|
|
-h|--help)
|
|
help
|
|
;;
|
|
-D|--debug)
|
|
DEBUG=1
|
|
;;
|
|
-f|--force-deactivate)
|
|
arg_force_deactivate=1
|
|
;;
|
|
-k|--kill)
|
|
arg_kill=1
|
|
;;
|
|
-r|--redirect)
|
|
arg_redirect_to_file=1
|
|
;;
|
|
-H|--host)
|
|
shift
|
|
BMC_ADDRESS=$1
|
|
;;
|
|
-U|--user)
|
|
shift
|
|
BMC_USER=$1
|
|
;;
|
|
-P|--password)
|
|
shift
|
|
BMC_PASSWORD=$1
|
|
;;
|
|
-l|--log*)
|
|
shift
|
|
LOG_FILE=$1
|
|
export LOG_FILE
|
|
;;
|
|
--rvmc-config)
|
|
shift
|
|
arg_rvmc_config=$1
|
|
;;
|
|
*)
|
|
die "Invalid argument '$1' [use -h/--help for help]"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
if ! hash ipmitool 2>&-; then
|
|
die "Cannot find 'ipmitool' in path. Is it installed?"
|
|
fi
|
|
if [ -n "${arg_rvmc_config}" ]; then
|
|
if [ ! -r "${arg_rvmc_config}" ]; then
|
|
die "RVMC config file does not exist or is not accessible: ${arg_rvmc_config}"
|
|
fi
|
|
BMC_ADDRESS=$(awk '/bmc_address:/ { print $2; }' "${arg_rvmc_config}")
|
|
[ -n "${BMC_ADDRESS}" ] || die "Could not set BMC_ADDRESS from ${arg_rvmc_config}"
|
|
BMC_USER=$(awk '/bmc_username:/ { print $2; }' "${arg_rvmc_config}")
|
|
[ -n "${BMC_USER}" ] || die "Could not set BMC_USER from ${arg_rvmc_config}"
|
|
BMC_PASSWORD=$(awk '/bmc_password:/ { print $2; }' "${arg_rvmc_config}" | base64 -d)
|
|
[ -n "${BMC_PASSWORD}" ] || die "Could not set BMC_PASSWORD from ${arg_rvmc_config}"
|
|
else
|
|
# Interactive. Prompt for BMC info:
|
|
[ -n "${BMC_ADDRESS}" ] || read -r -p "BMC address: " BMC_ADDRESS
|
|
[ -n "${BMC_USER}" ] || read -r -p "BMC user: " BMC_USER
|
|
[ -n "${BMC_PASSWORD}" ] || read -r -s -p "BMC password: " BMC_PASSWORD
|
|
fi
|
|
# Final check
|
|
[ -n "${BMC_ADDRESS}" ] || die "BMC_ADDRESS is empty"
|
|
[ -n "${BMC_USER}" ] || die "BMC_USER is empty"
|
|
[ -n "${BMC_PASSWORD}" ] || die "BMC_PASSWORD is empty"
|
|
export BMC_ADDRESS BMC_USER BMC_PASSWORD
|
|
|
|
if [ -n "${arg_kill}" ]; then
|
|
ipmitool_kill
|
|
exit $?
|
|
fi
|
|
|
|
log_progress "Capturing console output at: ${LOG_FILE}"
|
|
log_progress "To disconnect, use '<Enter>~.' or reinvoke this script with the --kill option."
|
|
|
|
# captures output in $LOG_FILE
|
|
if [ -n "${arg_redirect_to_file}" ]; then
|
|
redirect_output_to_file
|
|
else
|
|
tee_output_to_file
|
|
fi
|
|
|
|
if [ -n "${arg_force_deactivate}" ]; then
|
|
log_progress "Forcing deactivate"
|
|
ipmitool_deactivate
|
|
fi
|
|
|
|
# Ensure we disconnect on normal and abnormal termination signals:
|
|
trap do_cleanup INT QUIT TERM EXIT
|
|
|
|
ipmitool_activate
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
|
|
main "$@"
|
|
fi
|