#!/bin/bash # Fail early set -e set -o nounset set -o pipefail # Devices USB_DEV="/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AL0287C1-if00-port0" TAPE1_DEV="/dev/tape/by-id/scsi-35000000000000000-nst" TAPE2_DEV="/dev/tape/by-id/scsi-30000000000000000-nst" TAPE_COOL="60" # Mail parameters for tape alerts (cleaning etc.) MAIL_SENDER="Backup" MAIL_FROM="bareos@example.com" MAIL_TO="operator@example.com" HOST_NAME=$(hostname -f) # Misc umask 002 LOGGER="logger -i -t $(basename $0)" # USB needs serialization readonly LOCK_ROOT_DIR="/run/bareos" readonly LOCK_DIR="$LOCK_ROOT_DIR/.tape-helper-lock" readonly LOCK_TIMEOUT=50 # # Lock # lock() { if ! [ -w "$LOCK_ROOT_DIR" ]; then echo "Warning: Lock directory $LOCK_ROOT_DIR is not writable." | $LOGGER return fi for i in $(seq 1 $LOCK_TIMEOUT); do if mkdir "${LOCK_DIR}" 2>/dev/null; then break else if [ $i -eq $LOCK_TIMEOUT ]; then echo "Warning: Lock held for too long, trying to break it and continuing anyway." | $LOGGER unlock break else sleep 0.2 fi fi done } # # Unlock # unlock() { if [ -d "$LOCK_DIR" ] && [ -w "$LOCK_DIR" ]; then rmdir "$LOCK_DIR" fi } # # Set parameters # set_params() { # LTO-5 and LTO-6 have differing startup times case "$1" in 1|LTO-6*|"$TAPE1_DEV") ON="\xFF\x01\x01" OFF="\xFF\x01\x00" TAPE_DEV=$TAPE1_DEV TAPE_WAIT="180" ;; 2|LTO-5*|LTO-4*|"$TAPE2_DEV") ON="\xFF\x02\x01" OFF="\xFF\x02\x00" TAPE_DEV=$TAPE2_DEV TAPE_WAIT="90" ;; *) echo "Sorry, I don't know a drive called $1." | $LOGGER exit 1 ;; esac } # # Push the button # toggle_relais() { if [ -w "$USB_DEV" ]; then lock echo -e $ON > $USB_DEV sleep 0.5 echo -e $OFF > $USB_DEV unlock # Wait for drive to fully boot sleep $TAPE_WAIT elif ! [ -r "$USB_DEV" ]; then echo "Warning: USB relais box not found." | $LOGGER else echo "Not enough rights to write to $USB_DEV, aborting." | $LOGGER exit 1 fi } # # Help # print_help() { echo "tape-helper [tape drive] [on|off|health|mam]" echo " [create-snapshot|delete-snapshot] snapshot-name zfs-datasets..." echo "" echo "Valid tape drives: 1 (LTO-6), 2 (LTO-5)" echo "" echo "on .......... Turn on tape drive" echo "off ......... Turn off tape drive" echo "health ...... Print health stats to stdout" echo "mam ......... Read cartridge MAM and print contents" echo "" } tape_on() { # Do nothing if device already present if [ -e "$TAPE_DEV" ]; then : #echo "Warning: Tape drive already present." | $LOGGER else # Turn on drive toggle_relais fi # If tape device present, try to load tape if [ -w "$TAPE_DEV" ]; then mt -f "$TAPE_DEV" load 2>/dev/null || true fi } tape_eject() { if ! [ -e "$TAPE_DEV" ]; then echo "Tape drive not detected, doing nothing." | $LOGGER exit 0 fi # If tape online with tape loaded, rewind and unload ONLINE=$(mt -f "$TAPE_DEV" status 2>/dev/null | awk ' BEGIN { go=0; } /^General status bits on/ { go=1; } /ONLINE/ { for (i=1;i<=NF; i++) { if (go==1 && $i=="ONLINE") printf "%s",$i; } }') if [ -n "$ONLINE" ]; then mt -f "$TAPE_DEV" rewoffl 2>/dev/null || true fi } tape_off() { # Eject tape tape_eject # Wait a little for drive to cool sleep "$TAPE_COOL" # Turn off drive toggle_relais } sg_from_nst() { # Figure out sg device from nst device local NST=$(readlink -m $TAPE_DEV 2>/dev/null || true) NST=${NST/\/dev\/nst/\/dev\/st} SG=$(lsscsi -g | awk -v nst=$NST ' { i=NF-1 if ($i == nst) { printf "%s", $NF exit } }') if [ -z "$SG" ]; then echo "Error: Cannot find tape drive $TAPE_DEV to show health information." | $LOGGER exit 1 fi } #TapeAlert Errors (C=Critical, W=Warning, I=Informational): #[0x15] W: The tape drive is due for routine cleaning: # 1. Wait for the current operation to finish. # 2. The use a cleaning cartridge. # Check the tape drive users manual for device specific cleaning instructions. show_health() { sg_from_nst F="/run/bareos/tape-health-"$(basename $SG|tr -dc [a-z0-9])".txt" smartctl -H -H -i -l error -l tapedevstat $SG 2>&1 | tee "$F" || true if ! grep "^TapeAlert: OK" "$F" >/dev/null; then ( echo "To: ${MAIL_TO}" echo "Subject: Backup alert on host ${HOST_NAME}" echo "Content-Transfer-Encoding: quoted-printable" echo "Content-Type: text/plain; charset=\"utf-8\"" echo "" cat "$F" echo "" ) | sendmail -i -t -F "${MAIL_SENDER}" -f "${MAIL_FROM}" fi rm -f "$F" } read_mam() { sg_from_nst sg_raw -o - -r 1024 -t 60 -v $TAPE_DEV \ 8c 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 2>/dev/null | \ /etc/bareos/read_attribute.pl } # # Main # [ $# -eq 0 ] && { print_help; exit 1; } case "$1" in create-snapshot|delete-snapshot) [ $# -lt 3 ] && { print_help; exit 1; } [ "$1" = "create-snapshot" ] && mode="Create" || mode="Delete" snapshot_name=$(echo "$2" | tr -dc [a-zA-Z0-9-_]) shift 2 for d in $*; do dataset="$d" snapshot="$dataset@$snapshot_name" if zfs list -H -t snapshot -o name "$dataset" >/dev/null 2>&1; then if zfs list -H -t snapshot -o name "$snapshot" >/dev/null 2>&1; then [ "$mode" = "Create" ] && echo "Warning: Snapshot $snapshot exists, destroying and re-creating" | $LOGGER zfs destroy -d "$snapshot" else [ "$mode" = "Delete" ] && echo "Error: Cannot destroy snapshot $snapshot, does not exist" | $LOGGER fi [ "$mode" = "Create" ] && { zfs snapshot "$snapshot" || exit 1 } else echo "$mode snapshot error: Dataset $dataset does not exist" | $LOGGER fi done ;; *) [ $# -lt 2 ] && { print_help; exit 1; } set_params "$1" case "$2" in "on") tape_on ;; "off") tape_off ;; "eject") tape_eject ;; "health") show_health ;; "mam") read_mam ;; *) print_help ; exit 1 ;; esac ;; esac exit 0