#!/bin/bash # # t4_dump.sh - mysqldump the TerminalFour database (whole DB, one table, or several tables) # # Usage: # t4_dump.sh Dump the entire terminalfour database # t4_dump.sh table1 Dump a single table # t4_dump.sh table1 table2 table3 Dump multiple tables # t4_dump.sh -l List tables and exit # # Options: # -o DIR Output directory (default: current directory) # -n NAME Output filename (default: auto-generated with timestamp) # -z Compress output with gzip # -s Schema only (no data) # -l List tables in the database and exit # -h Show this help # set -o pipefail DB="terminalfour" OUTDIR="." OUTNAME="" COMPRESS=0 SCHEMA_ONLY=0 LIST_ONLY=0 usage() { sed -n '3,19p' "$0" | sed 's/^# \{0,1\}//' exit "${1:-0}" } die() { echo "ERROR: $*" >&2; exit 1; } # ---------- Parse options ---------- while getopts ":o:n:zslh" opt; do case "$opt" in o) OUTDIR="$OPTARG" ;; n) OUTNAME="$OPTARG" ;; z) COMPRESS=1 ;; s) SCHEMA_ONLY=1 ;; l) LIST_ONLY=1 ;; h) usage 0 ;; :) die "Option -$OPTARG requires an argument" ;; *) die "Unknown option -$OPTARG (use -h for help)" ;; esac done shift $((OPTIND - 1)) TABLES=("$@") command -v mysqldump >/dev/null 2>&1 || die "mysqldump not found in PATH" command -v mysql >/dev/null 2>&1 || die "mysql client not found in PATH" # ---------- Locate tomcat conf ---------- path="" for v in tomcat9 tomcat8 tomcat7; do if [[ -e "/usr/share/$v/conf/server.xml" ]]; then path="/usr/share/$v/conf" break fi done [[ -n "$path" ]] || die "No tomcat conf dir with server.xml found under /usr/share (tomcat7/8/9)" SERVER_XML="$path/server.xml" # ---------- Extract DB credentials ---------- # Pull the attribute value only; take the first match in case of multiple resources. get_attr() { grep -o "$1=\"[^\"]*\"" "$SERVER_XML" | head -n1 | sed -e "s/^$1=\"//" -e 's/"$//' } # Decode common XML entities (passwords in server.xml are often escaped) xml_decode() { sed -e 's/<//g' -e 's/"/"/g' -e "s/'/'/g" -e 's/&/\&/g' } url="$(get_attr url)" user="$(get_attr username | xml_decode)" pass="$(get_attr password | xml_decode)" # jdbc:mysql://host[:port]/terminalfour?params -> host[:port] hostport="$(echo "$url" | sed -e 's#^jdbc:mysql://##' -e 's#/.*##' | tr -d '[:space:]')" host="${hostport%%:*}" port="3306" [[ "$hostport" == *:* ]] && port="${hostport##*:}" [[ -n "$host" && -n "$user" ]] || die "Could not parse DB host/user from $SERVER_XML" # ---------- Write creds to a temporary defaults file ---------- # Keeps the password off the command line (not visible in `ps`) and avoids the # "Using a password on the command line" warning. CNF="$(mktemp)" || die "Could not create temp file" trap 'rm -f "$CNF"' EXIT chmod 600 "$CNF" cat > "$CNF" </dev/null 2>&1 \ || die "Cannot connect to $DB on $host:$port as $user" # ---------- List mode ---------- if [[ $LIST_ONLY -eq 1 ]]; then "${MYSQL[@]}" -N -e "SHOW TABLES" "$DB" exit $? fi # ---------- Validate requested tables ---------- if [[ ${#TABLES[@]} -gt 0 ]]; then existing="$("${MYSQL[@]}" -N -e "SHOW TABLES" "$DB")" || die "Could not list tables" missing=() for t in "${TABLES[@]}"; do grep -qxF "$t" <<< "$existing" || missing+=("$t") done [[ ${#missing[@]} -eq 0 ]] || die "Table(s) not found in $DB: ${missing[*]}" fi # ---------- Build output filename ---------- mkdir -p "$OUTDIR" || die "Could not create output dir $OUTDIR" ts="$(date +%Y%m%d_%H%M%S)" if [[ -z "$OUTNAME" ]]; then if [[ ${#TABLES[@]} -eq 0 ]]; then OUTNAME="${DB}_full_${ts}.sql" elif [[ ${#TABLES[@]} -eq 1 ]]; then OUTNAME="${DB}_${TABLES[0]}_${ts}.sql" else OUTNAME="${DB}_${#TABLES[@]}tables_${ts}.sql" fi fi [[ $COMPRESS -eq 1 && "$OUTNAME" != *.gz ]] && OUTNAME="${OUTNAME}.gz" OUTFILE="${OUTDIR%/}/$OUTNAME" # ---------- Dump options ---------- # --single-transaction : consistent snapshot of InnoDB tables without locking # --quick : stream rows instead of buffering whole tables in memory # --no-tablespaces : skip tablespace info (needs PROCESS privilege, which the # T4 DB user normally doesn't have; T4 doesn't use them) OPTS=(--single-transaction --quick --no-tablespaces --default-character-set=utf8mb4 --hex-blob) [[ $SCHEMA_ONLY -eq 1 ]] && OPTS+=(--no-data) # Include routines/triggers only on a full dump [[ ${#TABLES[@]} -eq 0 ]] && OPTS+=(--routines --triggers) # ---------- Run ---------- if [[ ${#TABLES[@]} -eq 0 ]]; then echo "Dumping entire '$DB' database from $host:$port ..." else echo "Dumping table(s) from '$DB': ${TABLES[*]} ..." fi start=$(date +%s) if [[ $COMPRESS -eq 1 ]]; then "${DUMP[@]}" "${OPTS[@]}" "$DB" "${TABLES[@]}" | gzip > "$OUTFILE" else "${DUMP[@]}" "${OPTS[@]}" "$DB" "${TABLES[@]}" > "$OUTFILE" fi rc=$? if [[ $rc -ne 0 ]]; then rm -f "$OUTFILE" die "mysqldump failed (exit $rc); partial file removed" fi end=$(date +%s) size="$(du -h "$OUTFILE" | cut -f1)" echo "Done in $((end - start))s -> $OUTFILE ($size)"