autopostgresqlbackup 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #!/bin/bash
  2. # {{{ License and Copyright
  3. # PostgreSQL Backup Script
  4. # https://github.com/k0lter/autopostgresqlbackup
  5. # Copyright (c) 2005 Aaron Axelsen <axelseaa@amadmax.com>
  6. # 2005 Friedrich Lobenstock <fl@fl.priv.at>
  7. # 2013-2023 Emmanuel Bouthenot <kolter@openics.org>
  8. #
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  22. # }}}
  23. # {{{ Variables
  24. # Email Address to send errors to
  25. MAILADDR="root"
  26. # By default, on Debian systems (and maybe others), only postgres user is
  27. # allowed to access PostgreSQL databases without password.
  28. #
  29. # In order to dump databases we need to run pg_dump/psql commands as postgres
  30. # with su. This setting makes possible to run backups with a substitute user
  31. # using su. If
  32. #
  33. # empty, su usage will be disabled)
  34. SU_USERNAME=
  35. # Username to access the PostgreSQL server
  36. USERNAME="postgres"
  37. # Password settings
  38. # In order to use a password to connect to database create a file
  39. # ${HOME}/.pgpass containing a line like this
  40. #
  41. # hostname:*:*:dbuser:dbpass
  42. #
  43. # replace hostname with the value of ${DBHOST}, dbuser with the value of
  44. # ${USERNAME} and dbpass with the password.
  45. # Host name (or IP address) of PostgreSQL server
  46. DBHOST="localhost"
  47. # Port of PostgreSQL server (only used if ${DBHOST} != localhost).
  48. DBPORT="5432"
  49. # List of database(s) names(s) to backup If you would like to backup all
  50. # databases on the server set DBNAMES="all".
  51. #
  52. # If set to "all" then any new databases will automatically be backed up
  53. # without needing to modify this settings when a new database is created.
  54. #
  55. # If the database you want to backup has a space in the name replace the space
  56. # with a % ("data base" will become "data%base").
  57. DBNAMES="all"
  58. # List of databases to exclude if DBNAMES is not set to all.
  59. DBEXCLUDE=""
  60. # Pseudo database name used to dump global objects (users, roles, tablespaces)
  61. GLOBALS_OBJECTS="postgres_globals"
  62. # Backup directory
  63. BACKUPDIR="/var/backups"
  64. # Include CREATE DATABASE in backups?
  65. CREATE_DATABASE="yes"
  66. # Which day do you want weekly backups? (1 to 7 where 1 is Monday)
  67. # When set to 0, weekly backups are disabled
  68. DOWEEKLY=7
  69. # Which day do you want monthly backups?
  70. # When set to 0, monthly backups are disabled
  71. DOMONTHLY=1
  72. # Backup retention count for daily backups, older backups are removed.
  73. BRDAILY=14
  74. # Backup retention count for weekly backups, older backups are removed.
  75. BRWEEKLY=5
  76. # Backup retention count for monthly backups, older backups are removed.
  77. BRMONTHLY=12
  78. # Compression tool. It could be gzip, pigz, bzip2, xz, zstd or any compression
  79. # tool that supports to read data to be compressed from stdin and outputs them
  80. # to stdout).
  81. # If the tool is not in ${PATH}, the absolute path can be used.
  82. COMP="gzip"
  83. # Compression tools options to be used with COMP
  84. COMP_OPTS=
  85. # Options string for use with pg_dump (see pg_dump manual page).
  86. OPT=
  87. # Backup files extension
  88. EXT="sql"
  89. # Backup files permission
  90. PERM=600
  91. # Enable encryption (asymmetric) with GnuPG.
  92. ENCRYPTION="no"
  93. # Encryption public key (path to the key)
  94. # Export your public key=""
  95. # gpg --export 0xY0URK3Y1D --output mypubkey.gpg or \
  96. # gpg --export --armor 0xY0URK3Y1D --output mypubkey.asc
  97. # then copy mypubkey.asc or mypubkey.gpg to the path pointed by the
  98. # ${ENCRYPTION_PUBLIC_KEY}.
  99. #
  100. # Decryption
  101. # gpg --decrypt --output backup.sql.gz backup.sql.gz.enc
  102. #
  103. ENCRYPTION_PUBLIC_KEY=
  104. # Suffix for encyrpted files
  105. ENCRYPTION_SUFFIX=".enc"
  106. # Command or script to execute before backups
  107. PREBACKUP=
  108. # Command or script to execute after backups
  109. POSTBACKUP=
  110. # }}}
  111. # {{{ OS Specific
  112. if [ -f /etc/default/autopostgresqlbackup ]; then
  113. # shellcheck source=/dev/null
  114. . /etc/default/autopostgresqlbackup
  115. fi
  116. # }}}
  117. # {{{ Defaults
  118. PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/postgres/bin:/usr/local/pgsql/bin
  119. HOMEPAGE="https://github.com/k0lter/autopostgresqlbackup"
  120. NAME="AutoPostgreSQLBackup" # Script name
  121. VERSION="2.0" # Version Number
  122. DATE="$(date '+%Y-%m-%d_%Hh%Mm')" # Datestamp e.g 2002-09-21
  123. DNOW="$(date '+%u')" # Day number of the week 1 to 7 where 1 represents Monday
  124. DNOM="$(date '+%d')" # Date of the Month e.g. 27
  125. LOG_DIR="${BACKUPDIR}" # Directory where the main log is saved
  126. # Fix day of month (left padding with 0)
  127. DOMONTHLY="$(echo "${DOMONTHLY}" | sed -r 's/^[0-9]$/0\0/')"
  128. # Using a shared memory filesystem (if available) to avoid
  129. # issues when there is no left space on backup storage
  130. if [ -w "/dev/shm" ]; then
  131. LOG_DIR="/dev/shm"
  132. fi
  133. LOG_FILE="${LOG_DIR}/${NAME}_${DBHOST//\//_}-$(date '+%Y-%m-%d_%Hh%Mm').log"
  134. # Debug mode
  135. DEBUG="no"
  136. # Encryption prerequisites
  137. GPG_HOMEDIR=
  138. # pg_dump options
  139. if [ -n "${OPT}" ]; then
  140. IFS=" " read -r -a PG_OPTIONS <<< "${OPT}"
  141. else
  142. PG_OPTIONS=()
  143. fi
  144. # Create required directories
  145. if [ ! -e "${BACKUPDIR}" ]; then # Check Backup Directory exists.
  146. mkdir -p "${BACKUPDIR}"
  147. fi
  148. if [ ! -e "${BACKUPDIR}/daily" ]; then # Check Daily Directory exists.
  149. mkdir -p "${BACKUPDIR}/daily"
  150. fi
  151. if [ ! -e "${BACKUPDIR}/weekly" ]; then # Check Weekly Directory exists.
  152. mkdir -p "${BACKUPDIR}/weekly"
  153. fi
  154. if [ ! -e "${BACKUPDIR}/monthly" ]; then # Check Monthly Directory exists.
  155. mkdir -p "${BACKUPDIR}/monthly"
  156. fi
  157. # Hostname for LOG information and
  158. # pg_dump{,all} connection settings
  159. if [ "${DBHOST}" = "localhost" ]; then
  160. HOST="$(hostname --fqdn)"
  161. PG_CONN=()
  162. else
  163. HOST="${DBHOST}:${DBPORT}"
  164. PG_CONN=(--host "${DBHOST}" --port "${DBPORT}")
  165. fi
  166. if [ -n "${USERNAME}" ]; then
  167. PG_CONN+=(--username "${USERNAME}")
  168. fi
  169. # }}}
  170. # {{{ log{,ger,_info,_debug,_warn,_error}()
  171. logger() {
  172. local fd line severity reset color
  173. fd="${1}"
  174. severity="${2}"
  175. reset=
  176. color=
  177. if [ -n "${TERM}" ]; then
  178. reset="\e[0m"
  179. case "${severity}" in
  180. error)
  181. color="\e[0;91m"
  182. ;;
  183. warn)
  184. color="\e[0;93m"
  185. ;;
  186. debug)
  187. color="\e[0;96m"
  188. ;;
  189. *)
  190. color="\e[0;94m"
  191. ;;
  192. esac
  193. fi
  194. while IFS= read -r line ; do
  195. printf "%s|%s|%s\n" "${fd}" "${severity}" "${line}" >> "${LOG_FILE}"
  196. if [ "${DEBUG}" = "yes" ]; then
  197. if [ "${fd}" = "out" ]; then
  198. printf "${color}%6s${reset}|%s\n" "${severity}" "${line}" >&6
  199. elif [ "${fd}" = "err" ]; then
  200. printf "${color}%6s${reset}|%s\n" "${severity}" "${line}" >&7
  201. fi
  202. fi
  203. done
  204. }
  205. log() {
  206. echo "$@" | logger "out" ""
  207. }
  208. log_debug() {
  209. echo "$@" | logger "out" "debug"
  210. }
  211. log_info() {
  212. echo "$@" | logger "out" "info"
  213. }
  214. log_error() {
  215. echo "$@" | logger "err" "error"
  216. }
  217. log_warn() {
  218. echo "$@" | logger "err" "warn"
  219. }
  220. # }}}
  221. # {{{ gpg_setup()
  222. gpg_setup() {
  223. GPG_HOMEDIR="$(mktemp --quiet --directory -t "${NAME}.XXXXXX")"
  224. chmod 700 "${GPG_HOMEDIR}"
  225. log_debug "With encryption enabled creating a temporary GnuPG home in ${GPG_HOMEDIR}"
  226. gpg --quiet --homedir "${GPG_HOMEDIR}" --quick-gen-key --batch --passphrase-file /dev/null "root@$(hostname --fqdn)"
  227. }
  228. # }}}
  229. # {{{ dblist()
  230. dblist () {
  231. local cmd_prog cmd_args raw_dblist dblist dbexcl databases
  232. cmd_prog="psql"
  233. cmd_args=(-t -l -A -F:)
  234. if [ "${#PG_CONN[@]}" -gt 0 ]; then
  235. cmd_args+=("${PG_CONN[@]}")
  236. fi
  237. log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
  238. raw_dblist=$(
  239. if [ -n "${SU_USERNAME}" ]; then
  240. su - "${SU_USERNAME}" -l -c "${cmd_prog} ${cmd_args[*]}"
  241. else
  242. "${cmd_prog}" "${cmd_args[@]}"
  243. fi
  244. )
  245. read -r -a dblist <<< "$(
  246. printf "%s" "${raw_dblist}" | \
  247. sed -r -n 's/^([^:]+):.+$/\1/p' | \
  248. tr '\n' ' '
  249. )"
  250. log_debug "Automatically found databases: ${dblist[*]}"
  251. if [ -n "${DBEXCLUDE}" ]; then
  252. IFS=" " read -r -a dbexcl <<< "${DBEXCLUDE}"
  253. else
  254. dbexcl=()
  255. fi
  256. dbexcl+=(template0)
  257. log_debug "Excluded databases: ${dbexcl[*]}"
  258. mapfile -t databases < <(
  259. comm -23 \
  260. <(IFS=$'\n'; echo "${dblist[*]}" | sort) \
  261. <(IFS=$'\n'; echo "${dbexcl[*]}" | sort) \
  262. )
  263. databases+=("${GLOBALS_OBJECTS}")
  264. log_debug "Database(s) to be backuped: ${databases[*]}"
  265. printf "%s " "${databases[@]}"
  266. }
  267. # }}}
  268. # {{{ dbdump()
  269. dbdump () {
  270. local db cmd_prog cmd_args pg_args
  271. db="${1}"
  272. pg_args="${PG_OPTIONS[*]}"
  273. if [ "${db}" = "${GLOBALS_OBJECTS}" ]; then
  274. cmd_prog="pg_dumpall"
  275. cmd_args=(--globals-only)
  276. else
  277. cmd_prog="pg_dump"
  278. cmd_args=("${DB}")
  279. if [ "${CREATE_DATABASE}" = "yes" ]; then
  280. pg_args+=(--create)
  281. fi
  282. fi
  283. if [ "${#PG_CONN[@]}" -gt 0 ]; then
  284. cmd_args+=("${PG_CONN[@]}")
  285. fi
  286. if [ "${#pg_args[@]}" -gt 0 ]; then
  287. cmd_args+=("${pg_args[@]}")
  288. fi
  289. log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
  290. if [ -n "${SU_USERNAME}" ]; then
  291. su - "${SU_USERNAME}" -l -c "${cmd_prog} ${cmd_args[*]}"
  292. else
  293. "${cmd_prog}" "${cmd_args[@]}"
  294. fi
  295. }
  296. # }}}
  297. # {{{ encryption()
  298. encryption() {
  299. log_debug "Encrypting using public key ${ENCRYPTION_PUBLIC_KEY}"
  300. gpg --homedir "${GPG_HOMEDIR}" --encrypt --passphrase-file /dev/null --recipient-file "${ENCRYPTION_PUBLIC_KEY}" 2>&7
  301. }
  302. # }}}
  303. # {{{ compression()
  304. compression () {
  305. if [ -n "${COMP_OPTS}" ]; then
  306. IFS=" " read -r -a comp_args <<< "${COMP_OPTS}"
  307. log_debug "Compressing using '${COMP} ${comp_args[*]}'"
  308. "${COMP}" "${comp_args[@]}" 2>&7
  309. else
  310. log_debug "Compressing using '${COMP}'"
  311. "${COMP}" 2>&7
  312. fi
  313. }
  314. # }}}
  315. # {{{ dump()
  316. dump() {
  317. local db_name dump_file comp_ext
  318. db_name="${1}"
  319. dump_file="${2}"
  320. if [ -n "${COMP}" ]; then
  321. comp_ext=".comp"
  322. case "${COMP}" in
  323. gzip|pigz)
  324. comp_ext=".gz"
  325. ;;
  326. bzip2)
  327. comp_ext=".bz2"
  328. ;;
  329. xz)
  330. comp_ext=".xz"
  331. ;;
  332. zstd)
  333. comp_ext=".zstd"
  334. ;;
  335. esac
  336. dump_file="${dump_file}${comp_ext}"
  337. fi
  338. if [ "${ENCRYPTION}" = "yes" ]; then
  339. dump_file="${dump_file}${ENCRYPTION_SUFFIX}"
  340. fi
  341. if [ -n "${COMP}" ] && [ "${ENCRYPTION}" = "yes" ]; then
  342. log_debug "Dumping (${db_name}) +compress +encrypt to '${dump_file}'"
  343. dbdump "${db_name}" | compression | encryption > "${dump_file}"
  344. elif [ -n "${COMP}" ]; then
  345. log_debug "Dumping (${db_name}) +compress to '${dump_file}'"
  346. dbdump "${db_name}" | compression > "${dump_file}"
  347. elif [ "${ENCRYPTION}" = "yes" ]; then
  348. log_debug "Dumping (${db_name}) +encrypt to '${dump_file}'"
  349. dbdump "${db_name}" | encryption > "${dump_file}"
  350. else
  351. log_debug "Dumping (${db_name}) to '${dump_file}'"
  352. dbdump "${db_name}" > "${dump_file}"
  353. fi
  354. if [ -f "${dump_file}" ]; then
  355. log_debug "Fixing permissions (${PERM}) on '${dump_file}'"
  356. chmod "${PERM}" "${dump_file}"
  357. if [ ! -s "${dump_file}" ]; then
  358. log_error "Something went wrong '${dump_file}' is empty (no space left on device?)"
  359. fi
  360. else
  361. log_error "Something went wrong '${dump_file}' does not exists (error during dump?)"
  362. fi
  363. }
  364. # }}}
  365. # {{{ cleanup()
  366. cleanup() {
  367. local dumpdir db when count line
  368. dumpdir="${1}"
  369. db="${2}"
  370. when="${3}"
  371. count="${4}"
  372. # Since version >= 2.0 the dump filename no longer contains the week number
  373. # or the abbreviated month name so in order to be sure to remove the older
  374. # dumps we need to sort the filename on the datetime part (YYYY-MM-DD_HHhMMm)
  375. log_info "Rotating ${count} ${when} backups..."
  376. log_debug "Looking for '${db}_*' in '${dumpdir}/${when}/${db}'"
  377. find "${dumpdir}/${when}/${db}/" -name "${db}_*" | \
  378. sed -r 's/^.+([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}h[0-9]{2}m).*$/\1 \0/' | \
  379. sort -r | \
  380. sed -r -n 's/\S+ //p' | \
  381. tail "+${count}" | \
  382. xargs -L1 rm -fv | \
  383. while IFS= read -r line ; do
  384. log_info "${line}"
  385. done
  386. }
  387. # }}}
  388. # {{{ usage()
  389. usage() {
  390. cat <<EOH
  391. USAGE: $(basename "$0") [OPTIONS]
  392. ${NAME} ${VERSION}
  393. A fully automated tool to make periodic backups of PostgreSQL databases.
  394. Options:
  395. -h Shows this help
  396. -d Run in debug mode (no mail sent)
  397. EOH
  398. }
  399. # }}}
  400. # {{{ Process command line arguments
  401. while getopts "hd" OPTION ; do
  402. case "${OPTION}" in
  403. h)
  404. usage
  405. exit 0
  406. ;;
  407. d)
  408. DEBUG="yes"
  409. ;;
  410. *)
  411. printf "Try \`%s -h\` to check the command line arguments\n" "$(basename "$0")" >&2
  412. exit 1
  413. esac
  414. done
  415. # }}}
  416. # {{{ I/O redirection(s) for logging
  417. exec 6>&1 # Link file descriptor #6 with stdout.
  418. # Saves stdout.
  419. exec 7>&2 # Link file descriptor #7 with stderr.
  420. # Saves stderr.
  421. exec > >( logger "out")
  422. exec 2> >( logger "err")
  423. # }}}
  424. # {{{ PreBackup
  425. # Run command before we begin
  426. if [ -n "${PREBACKUP}" ]; then
  427. log_info "Prebackup command output:"
  428. ${PREBACKUP} | \
  429. while IFS= read -r line ; do
  430. log " ${line}"
  431. done
  432. fi
  433. # }}}
  434. # {{{ main()
  435. log_info "${NAME} version ${VERSION}"
  436. log_info "Homepage: ${HOMEPAGE}"
  437. log_info "Backup of Database Server - ${HOST}"
  438. if [ -n "${COMP}" ]; then
  439. if ! command -v "${COMP}" >/dev/null ; then
  440. log_warn "Disabling compression, '${COMP}' command not found"
  441. unset COMP
  442. fi
  443. fi
  444. if [ "${ENCRYPTION}" = "yes" ]; then
  445. if [ ! -s "${ENCRYPTION_PUBLIC_KEY}" ]; then
  446. log_warn "Disabling encryption, '${ENCRYPTION_PUBLIC_KEY}' is empty or does not exists"
  447. ENCRYPTION="no"
  448. elif ! command -v "gpg" >/dev/null ; then
  449. log_warn "Disabling encryption, 'gpg' command not found"
  450. ENCRYPTION="no"
  451. else
  452. gpg_setup
  453. if ! keyinfo="$(gpg --quiet --homedir "${GPG_HOMEDIR}" "${ENCRYPTION_PUBLIC_KEY}" 2>/dev/null)"; then
  454. log_warn "Disabling encryption, key in '${ENCRYPTION_PUBLIC_KEY}' does not seems to be a valid public key"
  455. ENCRYPTION="no"
  456. if command -v "openssl" >/dev/null && openssl x509 -noout -in "${ENCRYPTION_PUBLIC_KEY}" >/dev/null 2>&1; then
  457. log_warn "public key in '${ENCRYPTION_PUBLIC_KEY}' seems to be in PEM format"
  458. log_warn "Encryption using openssl is no longer supported: see ${HOMEPAGE}#openssl-encryption"
  459. fi
  460. else
  461. keyfp="$(echo "${keyinfo}" | sed -r -n 's/^\s*([a-z0-9]+)\s*$/\1/pi')"
  462. keyuid="$(echo "${keyinfo}" | sed -r -n 's/^\s*uid\s+(\S.*)$/\1/pi' | head -n1)"
  463. log_info "Encryption public key is: 0x${keyfp} (${keyuid})"
  464. fi
  465. fi
  466. fi
  467. log_info "Backup Start: $(date)"
  468. if [ "${DNOM}" = "${DOMONTHLY}" ]; then
  469. period="monthly"
  470. rotate="${BRMONTHLY}"
  471. elif [ "${DNOW}" = "${DOWEEKLY}" ]; then
  472. period="weekly"
  473. rotate="${BRWEEKLY}"
  474. else
  475. period="daily"
  476. rotate="${BRDAILY}"
  477. fi
  478. # If backing up all DBs on the server
  479. if [ "${DBNAMES}" = "all" ]; then
  480. DBNAMES="$(dblist)"
  481. fi
  482. for db in ${DBNAMES} ; do
  483. db="${db//%/ / }"
  484. log_info "Backup of Database (${period}) '${db}'"
  485. backupdbdir="${BACKUPDIR}/${period}/${db}"
  486. if [ ! -e "${backupdbdir}" ]; then
  487. log_debug "Creating Backup DB directory '${backupdbdir}'"
  488. mkdir -p "${backupdbdir}"
  489. fi
  490. cleanup "${BACKUPDIR}" "${db}" "${period}" "${rotate}"
  491. backupfile="${backupdbdir}/${db}_${DATE}.${EXT}"
  492. dump "${db}" "${backupfile}"
  493. done
  494. log_info "Backup End: $(date)"
  495. log_info "Total disk space used for ${BACKUPDIR}: $(du -hs "${BACKUPDIR}" | cut -f1)"
  496. # }}}
  497. # {{{ PostBackup
  498. # Run command when we're done
  499. if [ -n "${POSTBACKUP}" ]; then
  500. log_info "Postbackup command output:"
  501. ${POSTBACKUP} | \
  502. while IFS= read -r line ; do
  503. log " ${line}"
  504. done
  505. fi
  506. # }}}
  507. # {{{ cleanup I/O redirections
  508. exec 1>&6 6>&- # Restore stdout and close file descriptor #6.
  509. exec 2>&7 7>&- # Restore stdout and close file descriptor #7.
  510. # }}}
  511. # {{{ Reporting
  512. if [ "${DEBUG}" = "no" ] && grep -q '^err|' "${LOG_FILE}" ; then
  513. (
  514. printf "*Errors/Warnings* (below) reported during backup on *%s*:\n\n" "${HOST}"
  515. grep '^err|' "${LOG_FILE}" | cut -d '|' -f 3- | \
  516. while IFS= read -r line ; do
  517. printf " | %s\n" "${line}"
  518. done
  519. printf "\n\nFull backup log follows:\n\n"
  520. grep -v '^...|debug|' "${LOG_FILE}" | \
  521. while IFS="|" read -r fd level line ; do
  522. if [ -n "${level}" ]; then
  523. printf "%8s| %s\n" "*${level}*" "${line}"
  524. else
  525. printf "%8s| %s\n" "" "${line}"
  526. fi
  527. done
  528. printf "\nFor more information, try to run %s in debug mode, see \`%s -h\`\n" "${NAME}" "$(basename "$0")"
  529. ) | mail -s "${NAME} issues on $(hostname --fqdn)" "${MAILADDR}"
  530. fi
  531. # }}}
  532. # {{{ Cleanup and exit()
  533. if [ -s "${LOGERR}" ]; then
  534. rc=1
  535. else
  536. rc=0
  537. fi
  538. # Cleanup GnuPG home dir
  539. if [ -d "${GPG_HOMEDIR}" ]; then
  540. rm -rf "${GPG_HOMEDIR}"
  541. fi
  542. # Clean up log files
  543. rm -f "${LOG_FILE}"
  544. exit ${rc}
  545. # }}}
  546. # vim: foldmethod=marker foldlevel=0 foldenable