→ Applies to: SynetoOS 5.x
Step 1. Connect to SynetoOS appliance via SSH as admin
ssh admin@<your_ip_address_or_hostname>
Step 2. Get root privileges
sudo su -
Step 3. Edit /tmp/generate-support-bundle.sh file
vi /tmp/generate-support-bundle.shIMPORTANT
Make sure to copy and paste the exact lines below.#!/usr/bin/env bash # This script dumps logs and system information into a .tar.gz archive # Logs are collected from various sources: # - Loki (via logcli) # - /var/log # - systemd (via journalctl) # - custom commands (e.g. `zpool history`, `df`, etc.) ################################################################################ # GLOBALS # ################################################################################ # Prevent masking erros in pipelines set -o pipefail # Signal handlers trap on_exit EXIT trap "echo -e '\nCancelled' >&2 ; exit 130" SIGINT # Terminate script when Ctrl-C is pressed export PATH=/usr/local/bin:$PATH # /usr/local/bin/logcli SCRIPT_NAME=$(basename "$0") DEFAULT_OUTPUT_DIR=/var/storage/support/bundles DEFAULT_LOGS_DURATION=1h ################################################################################ # FUNCTIONS # ################################################################################ function usage() { cat <<- EOF Usage: $SCRIPT_NAME [OPTIONS] [DIR] Generate support bundle. If no DIR is given, bundle is saved to $DEFAULT_OUTPUT_DIR and any file except the newly generated bundle is deleted to save space. Logs are collected from multiple sources: Loki, systemd, /var/log and ad hoc commands like \`zpool history\`. Options: -s, --since DURATION collect logs for the last DURATION hours or minutes (default: ${DEFAULT_LOGS_DURATION}); possible sufixes: "h" (hours), "m" (minutes); only affects Loki and systemd -p, --loki-port PORT forward PORT to port 3100 inside Loki pod (default: choose port automatically) -c, --no-strip-color do not strip color codes from collected files -j, --include-journal include binary logs from /var/log/journal in the bundle; this is usually not necessary as systemd logs are already collected as plaintext with \`journalctl\` -q, --quiet suppress non-error messages -h, --help display this help text and exit EOF } function on_exit() { # This function is invoked on script exit if [[ -n "$TEMP_DIR" ]]; then rm --recursive --force "$TEMP_DIR" &>/dev/null fi if [[ -n "$LOKI_PORT_FORWARD_PID" ]]; then kill "$LOKI_PORT_FORWARD_PID" &>/dev/null fi } function log() { # Print to stdout based on $QUIET flag if [[ -z "$QUIET" ]]; then echo "$@" fi } function error() { # Print to stderr echo "$@" >&2 } function start_loki_port_forward() { # Forward local port to port 3100 inside Loki pod. # If LOKI_PORT is non-empty, use its value for local port. # Otherwise, a free port is selected based on a retry mechanism. log "Starting Loki port forward" local start_port=${LOKI_PORT:-57343} local end_port=${LOKI_PORT:-57353} if [[ -n "$LOKI_PORT" ]]; then # If user specified a port, fail gracefully when that specific port is in use local pid if pid=$(pid_using_port "$LOKI_PORT"); then error "Port $LOKI_PORT is in use. Either try a different port or terminate process with PID $pid." exit 1 fi fi if ! retry_loki_port_forward "$start_port" "$end_port"; then error "Cannot connect to Loki. Check Loki pod status and/or port forwarding." exit 1 fi } function pid_using_port() { # Check if network port is in use. # Return 0 if port is in use, non-zero otherwise. # If port is in use, the PID of the process using it is printed. # # Parameters: # $1 port to check local port=$1 lsof -t -i ":$port" } function retry_loki_port_forward() { # Attempt to establish a kubectl port-forward to port 3100 inside Loki pod by trying a range of local ports. # Return 0 if port forward is successful *and* Loki API is ready, non-zero otherwise. # If successful, the env variable LOKI_ADDR is exported for logcli to use. # # Parameters: # $1 start port # $2 end port local start_port=$1 local end_port=$2 local port=$start_port local loki_addr while ((port <= end_port)); do if pid_using_port "$port" &>/dev/null; then ((port++)) continue fi { kubectl port-forward svc/loki -n monitoring "$port:3100" &>/dev/null & } &>/dev/null LOKI_PORT_FORWARD_PID=$! loki_addr="http://127.0.0.1:$port" if wait_loki "$loki_addr"; then export LOKI_ADDR=$loki_addr return 0 fi kill "$LOKI_PORT_FORWARD_PID" &>/dev/null ((port++)) done return 1 } function wait_loki() { # Check Loki API readiness # # Parameters: # $1 Loki address local loki_addr=$1 local max_attempts=3 local attempt=0 while ! curl --silent "$loki_addr/ready" | grep --quiet "ready"; do ((attempt++)) if ((attempt == max_attempts)); then return 1 fi sleep 1 done } function collect_loki_logs() { # Collect logs from Loki and save them into $TEMP_DIR/loki local output_dir=$TEMP_DIR/loki local duration=${LOGS_DURATION:-$DEFAULT_LOGS_DURATION} local query_args=(--quiet --no-labels --forward --limit=0 --since="$duration") log "Collecting Loki logs (duration: $duration)" mkdir "$output_dir" for label_type in "job" "unit" "filename"; do # Loop for each item in the label type list while read -r item; do query="{$label_type=\"$item\"}" if [[ "$label_type" == "filename" ]]; then logname=esxi-logs else logname=$(echo "$item" | cut --delimiter '/' --fields 2) fi # shellcheck disable=SC2086 logcli query "${query_args[@]}" "$query" >"$output_dir/$logname.log" done < <(logcli labels --quiet "$label_type") done } function collect_var_log() { # Collect logs from /var/log and save them into $TEMP_DIR/varlog log "Collecting /var/log" local output_dir=$TEMP_DIR/varlog local rsync_opts=(--quiet --recursive --times --prune-empty-dirs) if [[ -z "$INCLUDE_JOURNAL" ]]; then rsync_opts+=(--exclude /journal) fi mkdir "$output_dir" rsync "${rsync_opts[@]}" /var/log/ "$output_dir" } function log_command() { # Execute command and write its output to a file # # Parameters: # $1 command to run # $1 output file local command=$1 local output_file=$2 echo "Output of: $command" >"$output_file" echo >>"$output_file" eval "$command" >>"$output_file" 2>&1 } function collect_commands_output() { # Collect outputs of various commands and save them into $TEMP_DIR/commands log "Collecting commands output" local output_dir=$TEMP_DIR/commands mkdir "$output_dir" # Note: filenames must be unique inside output dir log_command "config support show" "$output_dir/config-support.txt" log_command "config firmware show" "$output_dir/config-firmware.txt" log_command "systemctl" "$output_dir/systemctl.txt" log_command "free --mebi" "$output_dir/free.txt" log_command "lsblk --json --output-all" "$output_dir/lsblk.txt" log_command "iostat" "$output_dir/iostat.txt" log_command "zpool list" "$output_dir/zpool-list.txt" log_command "zpool status" "$output_dir/zpool-status.txt" log_command "zdb | grep --extended-regexp 'ashift|\s+name'" "$output_dir/zdb-ashift.txt" log_command "zpool history" "$output_dir/zpool-history.txt" log_command "zfs list" "$output_dir/zfs-list.txt" log_command "df -h" "$output_dir/df.txt" log_command "config net dev show" "$output_dir/config-net-dev.txt" log_command "top -b -n1" "$output_dir/top.txt" log_command "arc_summary" "$output_dir/arc-summary.txt" } function create_archive() { # Create .tar.gz archive containing all collected files found in $TEMP_DIR. # If output directory is default, also perform a cleanup so as to delete all files except the newly created archive. log "Creating archive" local output_dir=${OUTPUT_DIR:-$DEFAULT_OUTPUT_DIR} local serial_number local archive_path serial_number=$(config firmware show | grep --ignore-case serial | awk --field-separator ':' '{print $2}' | xargs | sed 's/ /-/g') archive_path=$output_dir/support-bundle-$(date +%Y-%m-%d_%H-%M-%S)-$serial_number.tar.gz mkdir -p "$(dirname "$archive_path")" # We use --directory to suppress error 'tar: Removing leading `/' from member names' tar --create --use-compress-program=pigz --file "$archive_path" --directory "$TEMP_DIR" . if [[ -z "$OUTPUT_DIR" ]]; then # We do a cleanup only when using the default output dir log "Cleaning up $output_dir" find "$output_dir" -mindepth 1 ! -name "$(basename "$archive_path")" -delete fi log "Archive created: $(realpath "$archive_path")" } function strip_color_codes() { # Strip ANSI color codes from all files found in $TEMP_DIR log "Stripping color codes" local exp='s/\x1b\[[0-9;]*[mGKHF]//g' # https://superuser.com/a/380778 find "$TEMP_DIR" -type f -exec sed --in-place "$exp" '{}' + } function collect_journalctl_logs() { # Collect logs as generated by journalctl local duration=${LOGS_DURATION:-$DEFAULT_LOGS_DURATION} local journalctl_args=(--output=with-unit --since="-$duration") log "Collecting systemd logs (duration: $duration)" journalctl "${journalctl_args[@]}" >"$TEMP_DIR/journalctl.log" } function is_valid_duration() { # Validate duration value to be compatible with both logcli and journalctl # Acceptes suffixes: "h" (hours), "m" (minutes) # # Parameters: # $1 duration value (e.g. 24h) [[ "$1" =~ ^[1-9]+[0-9]*(h|m)$ ]] } ################################################################################ # MAIN # ################################################################################ # Check root user if [[ "$EUID" -ne 0 ]]; then error "This script must be run as root" exit 1 fi # Parse options if ! OPTIONS=$(getopt -o 'hqs:cjp:' --long 'help,quiet,since:,no-strip-color,include-journal,loki-port:' --name "$SCRIPT_NAME" -- "$@"); then error "Error while parsing script options" exit 1 fi eval set -- "$OPTIONS" while true; do case "$1" in '-h'|'--help') usage exit ;; '-s'|'--since') if ! is_valid_duration "$2"; then error "'$2' is not a valid duration" exit 1 fi LOGS_DURATION=$2 shift 2 continue ;; '-p'|'--loki-port') LOKI_PORT=$2 shift 2 continue ;; '-c'|'--no-strip-color') NO_STRIP_COLOR=1 shift continue ;; '-j'|'--include-journal') INCLUDE_JOURNAL=1 shift continue ;; '-q'|'--quiet') QUIET=1 shift continue ;; '--') shift break ;; *) error "Unknown option" usage exit 1 ;; esac done # Parse positional args if [[ $# -gt 1 ]]; then error "Too many arguments" usage exit 1 fi OUTPUT_DIR=${1%/} # Remove trailing slash if [[ -n "$OUTPUT_DIR" && ! -d "$OUTPUT_DIR" ]]; then error "Directory does not exist: $OUTPUT_DIR" exit 1 fi # Temporary dir where log files are collected if ! TEMP_DIR=$(mktemp --directory); then error "Failed to create temporary directory" exit 1 fi start_loki_port_forward collect_loki_logs collect_journalctl_logs collect_var_log collect_commands_output if [[ -z "$NO_STRIP_COLOR" ]]; then strip_color_codes fi create_archive
Save and EXIT
:wq
Step 4. Give permissions to /tmp/generate-support-bundle.sh file
chmod +x /tmp/generate-support-bundle.sh
Step 5. Generate the support bundle
./tmp/generate-support-bundle.shUse one of the following parameters for specific requirements
Options: -s, --since DURATION collect logs for the last DURATION hours or minutes (default: ${DEFAULT_LOGS_DURATION}); possible sufixes: "h" (hours), "m" (minutes); only affects Loki and systemd -p, --loki-port PORT forward PORT to port 3100 inside Loki pod (default: choose port automatically) -c, --no-strip-color do not strip color codes from collected files -j, --include-journal include binary logs from /var/log/journal in the bundle; this is usually not necessary as systemd logs are already collected as plaintext with \`journalctl\` -q, --quiet suppress non-error messages -h, --help display this help text and exit
Step 6 (optional). Upload support bundle ("How to Upload Files in SynetoOS 5")