autopostgresqlbackup 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  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. # Username to access the PostgreSQL server e.g. dbuser
  25. USERNAME=postgres
  26. # Password
  27. # create a file ${HOME}/.pgpass containing a line like this
  28. # hostname:*:*:dbuser:dbpass
  29. # replace hostname with the value of DBHOST and postgres with
  30. # the value of USERNAME
  31. # Host name (or IP address) of PostgreSQL server e.g localhost
  32. DBHOST=localhost
  33. # Port of PostgreSQL server e.g 5432 (only used if DBHOST != localhost)
  34. DBPORT=5432
  35. # List of DBNAMES for Daily/Weekly Backup e.g. "DB1 DB2 DB3"
  36. DBNAMES="all"
  37. # pseudo database name used to dump global objects (users, roles, tablespaces)
  38. GLOBALS_OBJECTS="postgres_globals"
  39. # Backup directory location e.g /backups
  40. BACKUPDIR="/var/backups"
  41. # Email Address to send mail to? (user@domain.com)
  42. MAILADDR="user@domain.com"
  43. # ============================================================
  44. # === ADVANCED OPTIONS ( Read the doc's below for details )===
  45. #=============================================================
  46. # List of DBNAMES to EXLUCDE if DBNAMES are set to all (must be in " quotes)
  47. DBEXCLUDE=""
  48. # Include CREATE DATABASE in backup?
  49. CREATE_DATABASE=yes
  50. # Which day do you want weekly backups? (1 to 7 where 1 is Monday)
  51. # When set to 0, weekly backups are disabled
  52. DOWEEKLY=6
  53. # Which day do you want monthly backups? (default is 1, first day of the month)
  54. # When set to 0, monthly backups are disabled
  55. DOMONTHLY=1
  56. # Backup retention count for daily backups
  57. # Default is 14 days
  58. BRDAILY=14
  59. # Backup retention count for weekly backups
  60. # Default is 5 weeks
  61. BRWEEKLY=5
  62. # Backup retention count for monthly backups
  63. # Default is 12 months
  64. BRMONTHLY=12
  65. # Choose Compression type. (gzip, pigz, bzip2, xz or zstd)
  66. COMP=gzip
  67. # Compression options
  68. COMP_OPTS=
  69. # OPT string for use with pg_dump (see man pg_dump)
  70. OPT=""
  71. # Backup files extension
  72. EXT="sql"
  73. # Backup files permission
  74. PERM=600
  75. # Encryption settings
  76. # (inspired by http://blog.altudov.com/2010/09/27/using-openssl-for-asymmetric-encryption-of-backups/)
  77. #
  78. # It is recommended to backup into a staging directory, and then use the
  79. # POSTBACKUP script to sync the encrypted files to the desired location.
  80. #
  81. # Encryption uses private/public keys. You can generate the key pairs like the following:
  82. # openssl req -x509 -nodes -days 100000 -newkey rsa:2048 -keyout backup.key -out backup.crt -subj '/'
  83. #
  84. # Decryption:
  85. # openssl smime -decrypt -in backup.sql.gz.enc -binary -inform DEM -inkey backup.key -out backup.sql.gz
  86. # Enable encryption
  87. ENCRYPTION=no
  88. # Encryption public key (path to the key)
  89. ENCRYPTION_PUBLIC_KEY=""
  90. # Encryption Cipher (see enc manpage)
  91. ENCRYPTION_CIPHER="aes256"
  92. # Suffix for encyrpted files
  93. ENCRYPTION_SUFFIX=".enc"
  94. # Command to run before backups (uncomment to use)
  95. #PREBACKUP="/etc/postgresql-backup-pre"
  96. # Command run after backups (uncomment to use)
  97. #POSTBACKUP="/etc/postgresql-backup-post"
  98. # }}}
  99. # {{{ OS Specific
  100. if [ -f /etc/default/autopostgresqlbackup ]; then
  101. # shellcheck source=/dev/null
  102. . /etc/default/autopostgresqlbackup
  103. fi
  104. # }}}
  105. # {{{ Documentation
  106. #=====================================================================
  107. # Options documentation
  108. #=====================================================================
  109. # Set USERNAME and PASSWORD of a user that has at least SELECT permission to
  110. # ALL databases.
  111. #
  112. # Set the DBHOST option to the server you wish to backup, leave the default to
  113. # backup "this server". To backup multiple servers make copies of this file and
  114. # set the options for that server.
  115. #
  116. # Put in the list of DBNAMES (Databases) to be backed up. If you would like to
  117. # backup ALL DBs on the server set DBNAMES="all". If set to "all" then any new
  118. # DBs will automatically be backed up without needing to modify this backup
  119. # script when a new DB is created.
  120. #
  121. # If the DB you want to backup has a space in the name replace the space with a
  122. # % e.g. "data base" will become "data%base"
  123. #
  124. # You can change the backup storage location to anything you like by using the
  125. # BACKUPDIR setting.
  126. #
  127. # === Advanced options doc's ===
  128. #
  129. # If you set DBNAMES="all" you can configure the option DBEXCLUDE. Other wise
  130. # this option will not be used. This option can be used if you want to backup
  131. # all dbs, but you want exclude some of them. (eg. if a db is to big).
  132. #
  133. # Set CREATE_DATABASE to "yes" (the default) if you want your SQL-Dump to
  134. # create a database with the same name as the original database when restoring.
  135. # Saying "no" here will allow your to specify the database name you want to
  136. # restore your dump into, making a copy of the database by using the dump
  137. # created with autopostgresqlbackup.
  138. #
  139. # Use PREBACKUP and POSTBACKUP to specify Per and Post backup commands
  140. # or scripts to perform tasks either before or after the backup process.
  141. #
  142. #=====================================================================
  143. # Backup Rotation..
  144. #=====================================================================
  145. #
  146. # Rotation is configurable for each period:
  147. # - daily (max $BRDAILY backups are keeped)
  148. # - weekly (max $BRWEEKLY backups are keeped)
  149. # - monthy (max $BRMONTHLY backups are keeped)
  150. #
  151. # }}}
  152. # {{{ Defaults
  153. PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/postgres/bin:/usr/local/pgsql/bin
  154. HOMEPAGE="https://github.com/k0lter/autopostgresqlbackup"
  155. NAME="AutoPostgreSQLBackup" # Script name
  156. VERSION="2.0" # Version Number
  157. DATE="$(date '+%Y-%m-%d_%Hh%Mm')" # Datestamp e.g 2002-09-21
  158. DNOW="$(date '+%u')" # Day number of the week 1 to 7 where 1 represents Monday
  159. DNOM="$(date '+%d')" # Date of the Month e.g. 27
  160. LOG_DIR="${BACKUPDIR}" # Directory where the main log is saved
  161. # Fix day of month (left padding with 0)
  162. DOMONTHLY="$(echo "${DOMONTHLY}" | sed -r 's/^[0-9]$/0\0/')"
  163. # Using a shared memory filesystem (if available) to avoid
  164. # issues when there is no left space on backup storage
  165. if [ -w "/dev/shm" ]; then
  166. LOG_DIR="/dev/shm"
  167. fi
  168. LOG_FILE="${LOG_DIR}/${NAME}_${DBHOST//\//_}-$(date '+%Y-%m-%d_%Hh%Mm').log"
  169. # Debug mode
  170. DEBUG="no"
  171. # pg_dump options
  172. if [ -n "${OPT}" ]; then
  173. IFS=" " read -r -a PG_OPTIONS <<< "${OPT}"
  174. else
  175. PG_OPTIONS=()
  176. fi
  177. # Create required directories
  178. if [ ! -e "${BACKUPDIR}" ]; then # Check Backup Directory exists.
  179. mkdir -p "${BACKUPDIR}"
  180. fi
  181. if [ ! -e "${BACKUPDIR}/daily" ]; then # Check Daily Directory exists.
  182. mkdir -p "${BACKUPDIR}/daily"
  183. fi
  184. if [ ! -e "${BACKUPDIR}/weekly" ]; then # Check Weekly Directory exists.
  185. mkdir -p "${BACKUPDIR}/weekly"
  186. fi
  187. if [ ! -e "${BACKUPDIR}/monthly" ]; then # Check Monthly Directory exists.
  188. mkdir -p "${BACKUPDIR}/monthly"
  189. fi
  190. # Hostname for LOG information and
  191. # pg_dump{,all} connection settings
  192. if [ "${DBHOST}" = "localhost" ]; then
  193. HOST="$(hostname --fqdn)"
  194. PG_CONN=()
  195. else
  196. HOST="${DBHOST}:${DBPORT}"
  197. PG_CONN=(--host "${DBHOST}" --port "${DBPORT}")
  198. fi
  199. if [ -n "${USERNAME}" ]; then
  200. PG_CONN+=(--username "${USERNAME}")
  201. fi
  202. # }}}
  203. # {{{ log{,ger,_info,_debug,_warn,_error}()
  204. logger() {
  205. local fd line severity reset color
  206. fd="${1}"
  207. severity="${2}"
  208. reset=
  209. color=
  210. if [ -n "${TERM}" ]; then
  211. reset="\e[0m"
  212. case "${severity}" in
  213. error)
  214. color="\e[0;91m"
  215. ;;
  216. warn)
  217. color="\e[0;93m"
  218. ;;
  219. debug)
  220. color="\e[0;96m"
  221. ;;
  222. *)
  223. color="\e[0;94m"
  224. ;;
  225. esac
  226. fi
  227. while IFS= read -r line ; do
  228. printf "%s|%s|%s\n" "${fd}" "${severity}" "${line}" >> "${LOG_FILE}"
  229. if [ "${DEBUG}" = "yes" ]; then
  230. if [ "${fd}" = "out" ]; then
  231. printf "${color}%6s${reset}|%s\n" "${severity}" "${line}" >&6
  232. elif [ "${fd}" = "err" ]; then
  233. printf "${color}%6s${reset}|%s\n" "${severity}" "${line}" >&7
  234. fi
  235. fi
  236. done
  237. }
  238. log() {
  239. echo "$@" | logger "out" ""
  240. }
  241. log_debug() {
  242. echo "$@" | logger "out" "debug"
  243. }
  244. log_info() {
  245. echo "$@" | logger "out" "info"
  246. }
  247. log_error() {
  248. echo "$@" | logger "err" "error"
  249. }
  250. log_warn() {
  251. echo "$@" | logger "err" "warn"
  252. }
  253. # }}}
  254. # {{{ dblist()
  255. dblist () {
  256. local cmd_prog cmd_args raw_dblist dblist dbexcl databases
  257. cmd_prog="psql"
  258. cmd_args=(-t -l -A -F:)
  259. if [ "${#PG_CONN[@]}" -gt 0 ]; then
  260. cmd_args+=("${PG_CONN[@]}")
  261. fi
  262. log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
  263. raw_dblist=$(
  264. if [ -n "${SU_USERNAME}" ]; then
  265. su - "${SU_USERNAME}" -l -c "${cmd_prog} ${cmd_args[*]}"
  266. else
  267. "${cmd_prog}" "${cmd_args[@]}"
  268. fi
  269. )
  270. read -r -a dblist <<< "$(
  271. printf "%s" "${raw_dblist}" | \
  272. sed -r -n 's/^([^:]+):.+$/\1/p' | \
  273. tr '\n' ' '
  274. )"
  275. log_debug "Automatically found databases: ${dblist[*]}"
  276. if [ -n "${DBEXCLUDE}" ]; then
  277. IFS=" " read -r -a dbexcl <<< "${DBEXCLUDE}"
  278. else
  279. dbexcl=()
  280. fi
  281. dbexcl+=(template0)
  282. log_debug "Excluded databases: ${dbexcl[*]}"
  283. mapfile -t databases < <(
  284. comm -23 \
  285. <(IFS=$'\n'; echo "${dblist[*]}" | sort) \
  286. <(IFS=$'\n'; echo "${dbexcl[*]}" | sort) \
  287. )
  288. databases+=("${GLOBALS_OBJECTS}")
  289. log_debug "Database(s) to be backuped: ${databases[*]}"
  290. printf "%s " "${databases[@]}"
  291. }
  292. # }}}
  293. # {{{ dbdump()
  294. dbdump () {
  295. local db cmd_prog cmd_args pg_args
  296. db="${1}"
  297. pg_args="${PG_OPTIONS[*]}"
  298. if [ "${db}" = "${GLOBALS_OBJECTS}" ]; then
  299. cmd_prog="pg_dumpall"
  300. cmd_args=(--globals-only)
  301. else
  302. cmd_prog="pg_dump"
  303. cmd_args=("${DB}")
  304. if [ "${CREATE_DATABASE}" = "yes" ]; then
  305. pg_args+=(--create)
  306. fi
  307. fi
  308. if [ "${#PG_CONN[@]}" -gt 0 ]; then
  309. cmd_args+=("${PG_CONN[@]}")
  310. fi
  311. if [ "${#pg_args[@]}" -gt 0 ]; then
  312. cmd_args+=("${pg_args[@]}")
  313. fi
  314. log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
  315. if [ -n "${SU_USERNAME}" ]; then
  316. su - "${SU_USERNAME}" -l -c "${cmd_prog} ${cmd_args[*]}"
  317. else
  318. "${cmd_prog}" "${cmd_args[@]}"
  319. fi
  320. }
  321. # }}}
  322. # {{{ encryption()
  323. encryption() {
  324. log_debug "Encrypting using cypher ${ENCRYPTION_CIPHER} and public key ${ENCRYPTION_PUBLIC_KEY}"
  325. openssl smime -encrypt -${ENCRYPTION_CIPHER} -binary -outform DEM "${ENCRYPTION_PUBLIC_KEY}" 2>&7
  326. }
  327. # }}}
  328. # {{{ compression()
  329. compression () {
  330. if [ -n "${COMP_OPTS}" ]; then
  331. IFS=" " read -r -a comp_args <<< "${COMP_OPTS}"
  332. log_debug "Compressing using '${COMP} ${comp_args[*]}'"
  333. "${COMP}" "${comp_args[@]}" 2>&7
  334. else
  335. log_debug "Compressing using '${COMP}'"
  336. "${COMP}" 2>&7
  337. fi
  338. }
  339. # }}}
  340. # {{{ dump()
  341. dump() {
  342. local db_name dump_file comp_ext
  343. db_name="${1}"
  344. dump_file="${2}"
  345. if [ -n "${COMP}" ]; then
  346. comp_ext=".comp"
  347. case "${COMP}" in
  348. gzip|pigz)
  349. comp_ext=".gz"
  350. ;;
  351. bzip2)
  352. comp_ext=".bz2"
  353. ;;
  354. xz)
  355. comp_ext=".xz"
  356. ;;
  357. zstd)
  358. comp_ext=".zstd"
  359. ;;
  360. esac
  361. dump_file="${dump_file}${comp_ext}"
  362. fi
  363. if [ "${ENCRYPTION}" = "yes" ]; then
  364. dump_file="${dump_file}${ENCRYPTION_SUFFIX}"
  365. fi
  366. if [ -n "${COMP}" ] && [ "${ENCRYPTION}" = "yes" ]; then
  367. log_debug "Dumping (${db_name}) +compress +encrypt to '${dump_file}'"
  368. dbdump "${db_name}" | compression | encryption > "${dump_file}"
  369. elif [ -n "${COMP}" ]; then
  370. log_debug "Dumping (${db_name}) +compress to '${dump_file}'"
  371. dbdump "${db_name}" | compression > "${dump_file}"
  372. elif [ "${ENCRYPTION}" = "yes" ]; then
  373. log_debug "Dumping (${db_name}) +encrypt to '${dump_file}'"
  374. dbdump "${db_name}" | encryption > "${dump_file}"
  375. else
  376. log_debug "Dumping (${db_name}) to '${dump_file}'"
  377. dbdump "${db_name}" > "${dump_file}"
  378. fi
  379. if [ -f "${dump_file}" ]; then
  380. log_debug "Fixing permissions (${PERM}) on '${dump_file}'"
  381. chmod "${PERM}" "${dump_file}"
  382. if [ ! -s "${dump_file}" ]; then
  383. log_error "Something went wrong '${dump_file}' is empty (no space left on device?)"
  384. fi
  385. else
  386. log_error "Something went wrong '${dump_file}' does not exists (error during dump?)"
  387. fi
  388. }
  389. # }}}
  390. # {{{ cleanup()
  391. cleanup() {
  392. local dumpdir db when count line
  393. dumpdir="${1}"
  394. db="${2}"
  395. when="${3}"
  396. count="${4}"
  397. # Since version >= 2.0 the dump filename no longer contains the week number
  398. # or the abbreviated month name so in order to be sure to remove the older
  399. # dumps we need to sort the filename on the datetime part (YYYY-MM-DD_HHhMMm)
  400. log_info "Rotating ${count} ${when} backups..."
  401. log_debug "Looking for '${db}_*' in '${dumpdir}/${when}/${db}'"
  402. find "${dumpdir}/${when}/${db}/" -name "${db}_*" | \
  403. sed -r 's/^.+([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}h[0-9]{2}m).*$/\1 \0/' | \
  404. sort -r | \
  405. sed -r -n 's/\S+ //p' | \
  406. tail "+${count}" | \
  407. xargs -L1 rm -fv | \
  408. while IFS= read -r line ; do
  409. log_info "${line}"
  410. done
  411. }
  412. # }}}
  413. # {{{ usage()
  414. usage() {
  415. cat <<EOH
  416. USAGE: $(basename "$0") [OPTIONS]
  417. ${NAME} ${VERSION}
  418. A fully automated tool to make periodic backups of PostgreSQL databases.
  419. Options:
  420. -h Shows this help
  421. -d Run in debug mode (no mail sent)
  422. EOH
  423. }
  424. # }}}
  425. # {{{ Process command line arguments
  426. while getopts "hd" OPTION ; do
  427. case "${OPTION}" in
  428. h)
  429. usage
  430. exit 0
  431. ;;
  432. d)
  433. DEBUG="yes"
  434. ;;
  435. *)
  436. printf "Try \`%s -h\` to check the command line arguments\n" "$(basename "$0")" >&2
  437. exit 1
  438. esac
  439. done
  440. # }}}
  441. # {{{ I/O redirection(s) for logging
  442. exec 6>&1 # Link file descriptor #6 with stdout.
  443. # Saves stdout.
  444. exec 7>&2 # Link file descriptor #7 with stderr.
  445. # Saves stderr.
  446. exec > >( logger "out")
  447. exec 2> >( logger "err")
  448. # }}}
  449. # {{{ PreBackup
  450. # Run command before we begin
  451. if [ -n "${PREBACKUP}" ]; then
  452. log_info "Prebackup command output:"
  453. ${PREBACKUP} | \
  454. while IFS= read -r line ; do
  455. log " ${line}"
  456. done
  457. fi
  458. # }}}
  459. # {{{ main()
  460. log_info "${NAME} version ${VERSION}"
  461. log_info "Homepage: ${HOMEPAGE}"
  462. log_info "Backup of Database Server - ${HOST}"
  463. if [ -n "${COMP}" ]; then
  464. if ! command -v "${COMP}" >/dev/null ; then
  465. log_warn "Disabling compression, '${COMP}' command not found"
  466. unset COMP
  467. fi
  468. fi
  469. if [ "${ENCRYPTION}" = "yes" ] && ! command -v "openssl" >/dev/null ; then
  470. log_warn "Disabling encryption, 'openssl' command not found"
  471. ENCRYPTION="no"
  472. fi
  473. log_info "Backup Start: $(date)"
  474. if [ "${DNOM}" = "${DOMONTHLY}" ]; then
  475. period="monthly"
  476. rotate="${BRMONTHLY}"
  477. elif [ "${DNOW}" = "${DOWEEKLY}" ]; then
  478. period="weekly"
  479. rotate="${BRWEEKLY}"
  480. else
  481. period="daily"
  482. rotate="${BRDAILY}"
  483. fi
  484. # If backing up all DBs on the server
  485. if [ "${DBNAMES}" = "all" ]; then
  486. DBNAMES="$(dblist)"
  487. fi
  488. for db in ${DBNAMES} ; do
  489. db="${db//%/ / }"
  490. log_info "Backup of Database (${period}) '${db}'"
  491. backupdbdir="${BACKUPDIR}/${period}/${db}"
  492. if [ ! -e "${backupdbdir}" ]; then
  493. log_debug "Creating Backup DB directory '${backupdbdir}'"
  494. mkdir -p "${backupdbdir}"
  495. fi
  496. cleanup "${BACKUPDIR}" "${db}" "${period}" "${rotate}"
  497. backupfile="${backupdbdir}/${db}_${DATE}.${EXT}"
  498. dump "${db}" "${backupfile}"
  499. done
  500. log_info "Backup End: $(date)"
  501. log_info "Total disk space used for ${BACKUPDIR}: $(du -hs "${BACKUPDIR}" | cut -f1)"
  502. # }}}
  503. # {{{ PostBackup
  504. # Run command when we're done
  505. if [ -n "${POSTBACKUP}" ]; then
  506. log_info "Postbackup command output:"
  507. ${POSTBACKUP} | \
  508. while IFS= read -r line ; do
  509. log " ${line}"
  510. done
  511. fi
  512. # }}}
  513. # {{{ cleanup I/O redirections
  514. exec 1>&6 6>&- # Restore stdout and close file descriptor #6.
  515. exec 2>&7 7>&- # Restore stdout and close file descriptor #7.
  516. # }}}
  517. # {{{ Reporting
  518. if [ "${DEBUG}" = "no" ] && grep -q '^err|' "${LOG_FILE}" ; then
  519. (
  520. printf "*Errors/Warnings* (below) reported during backup on *%s*:\n\n" "${HOST}"
  521. grep '^err|' "${LOG_FILE}" | cut -d '|' -f 3- | \
  522. while IFS= read -r line ; do
  523. printf " | %s\n" "${line}"
  524. done
  525. printf "\n\nFull backup log follows:\n\n"
  526. grep -v '^...|debug|' "${LOG_FILE}" | \
  527. while IFS="|" read -r fd level line ; do
  528. if [ -n "${level}" ]; then
  529. printf "%8s| %s\n" "*${level}*" "${line}"
  530. else
  531. printf "%8s| %s\n" "" "${line}"
  532. fi
  533. done
  534. printf "\nFor more information, try to run %s in debug mode, see \`%s -h\`\n" "${NAME}" "$(basename "$0")"
  535. ) | mail -s "${NAME} - log" "${MAILADDR}"
  536. fi
  537. # }}}
  538. # {{{ Cleanup and exit()
  539. if [ -s "${LOGERR}" ]; then
  540. rc=1
  541. else
  542. rc=0
  543. fi
  544. # Clean up log files
  545. rm -f "${LOG_FILE}"
  546. exit ${rc}
  547. # }}}
  548. # vim: foldmethod=marker foldlevel=0 foldenable