|
#!/bin/bash |
|
|
|
VERBOSITY=0 |
|
TEMP_D="" |
|
DEFAULT_OUT="debug.tar.gz" |
|
|
|
error() { echo "$@" 1>&2; } |
|
fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; } |
|
failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; } |
|
|
|
Usage() { |
|
cat <<EOF |
|
Usage: ${0##*/} [ options ] [output] |
|
|
|
collect cloud-init debug information from a system. |
|
|
|
output defaults: $DEFAULT_OUT |
|
|
|
options: |
|
-h | --help show this ssage |
|
--all collect all information, including possibly sensitive |
|
info in /var/lib/cloud/ |
|
EOF |
|
} |
|
|
|
sensitive_message() { |
|
cat <<EOF |
|
****************************** Warning ********************************** |
|
Passing the --all flag means that the contents of /var/lib/cloud/ have |
|
been collected. If you launched this instance with sensitive information |
|
such as passwords or api keys, then do not post it publicly. |
|
EOF |
|
} |
|
|
|
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; } |
|
cleanup() { |
|
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}" |
|
} |
|
|
|
debug() { |
|
local level=${1}; shift; |
|
[ "${level}" -gt "${VERBOSITY}" ] && return |
|
error "${@}" |
|
} |
|
|
|
missing() { |
|
debug 1 "$@" |
|
echo "$@" >> "${TEMP_D}/missing.txt" |
|
} |
|
|
|
tar_dir() { |
|
local dir="$1" out="$2" |
|
if [ ! -d "$dir" ]; then |
|
missing "$out: $dir: not a directory" |
|
return |
|
fi |
|
debug 1 "collecting $dir to $out" |
|
local updir="" bdir="" |
|
updir=$(cd "$dir/.." && pwd) || |
|
{ error "failed cd $dir/.."; return 1; } |
|
bdir=$(basename "$dir") |
|
tar -C "$updir" -cf "${TEMP_D}/$out" "$bdir/" || |
|
{ error "failed to collect $dir"; return 1; } |
|
} |
|
|
|
collect_sys() { |
|
local root="$1" dir="$2" ofile="$3" |
|
debug 1 "collecting sys dir $dir" |
|
if [ "$root" != "/" ]; then |
|
missing "$ofile: root not /" |
|
return |
|
fi |
|
if [ ! -d "$dir" ]; then |
|
missing "$ofile: $dir not a dir." |
|
fi |
|
( cd "$dir" && grep -r . * ) 2>/dev/null > "${TEMP_D}/$ofile" |
|
return 0 |
|
} |
|
|
|
collect_files() { |
|
local root="$1" file="" ofile="" fofile="" |
|
shift |
|
for file in "$@"; do |
|
ofile="${file#${root}}" |
|
fofile="${TEMP_D}/files/${ofile}" |
|
debug 1 "collecting /$ofile" |
|
if [ ! -f "$file" ]; then |
|
missing "$ofile: not a file." |
|
fi |
|
[ -d "${fofile%/*}" ] || mkdir -p "${fofile%/*}" || return |
|
cp -a "$file" "$fofile" || return |
|
done |
|
} |
|
|
|
main() { |
|
local short_opts="hv" |
|
local long_opts="all,help,root:,verbose" |
|
local getopt_out="" |
|
getopt_out=$(getopt --name "${0##*/}" \ |
|
--options "${short_opts}" --long "${long_opts}" -- "$@") && |
|
eval set -- "${getopt_out}" || |
|
{ bad_Usage; return; } |
|
|
|
local cur="" next="" all=false output="${DEFAULT_OUT}" root="/" |
|
while [ $# -ne 0 ]; do |
|
cur="$1"; next="$2"; |
|
case "$cur" in |
|
--all) all=true;; |
|
-h|--help) Usage ; exit 0;; |
|
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));; |
|
-r|--root) root="$1"; shift;; |
|
--) shift; break;; |
|
esac |
|
shift; |
|
done |
|
|
|
[ $# -eq 0 -o $# -eq 1 ] || |
|
{ bad_Usage "expected 0 or 1 arguments, got $#: $*"; return; } |
|
if [ $# -eq 1 ]; then |
|
output="$1" |
|
fi |
|
if [ ! -d "$root" ]; then |
|
fail "target '$target' not a directory" |
|
fi |
|
root=$(cd "$root" && pwd) || fail "failed to get full path to root"; |
|
[ "$root" = "/" ] || root="$root/" |
|
|
|
if [ "$(id -u)" != "0" ]; then |
|
fail "Sorry, this program must be run as root: sudo $0 $*" |
|
fi |
|
|
|
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || |
|
fail "failed to make tempdir" |
|
trap cleanup EXIT |
|
|
|
if $all; then |
|
tar_dir "${root}var/lib/cloud" "var-lib-cloud.tar" || return |
|
fi |
|
|
|
tar_dir "${root}etc/cloud" "etc-cloud.tar" || return |
|
collect_sys "$root" "/sys/class/dmi/id" sys-class-dmi-id || return |
|
collect_sys "$root" "/sys/hypervisor" sys-hypervisor || return |
|
|
|
ofile="run-cloud-init.tar" |
|
if [ "$root" = "/" ]; then |
|
tar_dir "${root}run/cloud-init" "$ofile" || return |
|
else |
|
missing "$ofile: root not /" |
|
fi |
|
|
|
if [ "$root" = "/" -a -e /run/systemd ]; then |
|
debug 1 "collecting systemd logs" |
|
ofile="${TEMP_D}/journalctl-short-monotonic" |
|
journalctl -o short-monotonic > "$ofile" 2>&1 |
|
echo "$?" >> "$ofile" |
|
ofile="${TEMP_D}/journalctl-short-precise" |
|
journalctl -o short-precise > "$ofile" 2>&1 |
|
echo "$?" >> "$ofile" |
|
ofile="${TEMP_D}/systemctl-all" |
|
systemctl --all --no-pager > "$ofile" 2>&1 |
|
echo "$?" >> "$ofile" |
|
elif [ "$root" != "/" ]; then |
|
missing "journalctl: root not /" |
|
else |
|
missing "journalctl: not systemd." |
|
fi |
|
|
|
collect_files "$root" "${root}etc/fstab" "${root}var/log/cloud-init"* |
|
|
|
if [ "$root" = "/" ]; then |
|
local version="" |
|
{ |
|
echo "rpm: $(rpm -q cloud-init 2>/dev/null)" |
|
echo "dpkg: $(dpkg-query --show cloud-init 2>/dev/null)" |
|
echo "cloud-init: $(cloud-init --version 2>/dev/null)" |
|
} > "${TEMP_D}/version" |
|
else |
|
missing "version: root not /" |
|
fi |
|
|
|
if [ "$root" = "/" ]; then |
|
dmesg > "${TEMP_D}/dmesg" |
|
else |
|
missing "dmesg: root not /" |
|
fi |
|
|
|
uname -a > "${TEMP_D}/uname-a" |
|
local out="" |
|
if out=$(command -v systemd-detect-virt >/dev/null 2>&1); then |
|
systemd-detect-virt > "${TEMP_D}/systemd-detect-virt" |
|
else |
|
missing "systemd-detect-virt: command not available." |
|
fi |
|
uname -a > "${TEMP_D}/uname-a" |
|
|
|
if $all; then |
|
sensitive_message 1>&2 |
|
fi |
|
|
|
tar -C "${TEMP_D}" -czf "$output" . || |
|
fail "failed to write to $output" |
|
|
|
error "wrote to $output" |
|
} |
|
|
|
main "$@" |
|
# vi: ts=4 expandtab |