autopostgresqlbackup 19 KB

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