@@ -28,8 +28,9 @@ Reset='\e[0m'
2828# colored messages
2929echoerr () { echo -e " ${Red} $@ ${Reset} " 1>&2 ; } # ошибки
3030echowarn () { echo -e " ${Yellow} $@ ${Reset} " 1>&2 ; } # предупреждения
31- echoinfo () { echo -e " ${White} $@ ${Reset} " ; } # важные сообщения
32- echosucc () { echo -e " ${Green} $@ ${Reset} " ; } # сообщения об успехе
31+ echohead () { echo -e " ${Blue} $@ ${Reset} " ; } # заголовок или этап
32+ echoinfo () { echo -e " ${White} $@ ${Reset} " ; } # важные сообщения
33+ echosucc () { echo -e " ${Green} $@ ${Reset} " ; } # сообщения об успехе
3334
3435# функция подсчитывает длительность (day:hh:mm:ss) между временными метками в Unixtime
3536elapsed () {
@@ -59,8 +60,14 @@ if test "$(whoami)" != "postgres"; then
5960elif ! grep -q -w " $PG_USERNAME " " $PG_PASS_FILE " ; then
6061 echoerr " pg_backup: file '$PG_PASS_FILE ' must contain record for user '$PG_USERNAME '"
6162 exit 1
62- elif test " $GPG_PASSPHRASE " = " *censored*" ; then
63- echoerr " pg_backup: change default value of GPG_PASSPHRASE in '$SCRIPT_DIR /pg_backup.conf'"
63+ elif ! (echo " $PG_AMCHECK_VALIDATE " | grep -qoP ' ^[01]$' ); then
64+ echoerr " pg_backup: '$SCRIPT_DIR /pg_backup.conf': incorrect value of PG_AMCHECK_VALIDATE, expected 0 or 1"
65+ exit 1
66+ elif ! (echo " $GPG_ENCRYPT " | grep -qoP ' ^[01]$' ); then
67+ echoerr " pg_backup: '$SCRIPT_DIR /pg_backup.conf': incorrect value of GPG_PASSPHRASE, expected 0 or 1"
68+ exit 1
69+ elif test " $GPG_ENCRYPT " = 1 && test " $GPG_PASSPHRASE " = " *censored*" ; then
70+ echoerr " pg_backup: '$SCRIPT_DIR /pg_backup.conf': change default value of GPG_PASSPHRASE"
6471 exit 1
6572elif test ! -d " $BACKUP_DIR " ; then
6673 echoerr " pg_backup: directory '$BACKUP_DIR ' does not exist"
@@ -121,7 +128,7 @@ elif test "${1:-}" = "restore"; then
121128 if test -f " $BACKUP_FILE_OR_DIR " ; then
122129 BACKUP_FILE=" $BACKUP_FILE_OR_DIR "
123130 elif test -d " $BACKUP_FILE_OR_DIR " ; then
124- BACKUP_FILE=$( find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name " base.tar.*" -printf " %p" )
131+ BACKUP_FILE=$( find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name " base.tar.*" ! -name " *.log " -printf " %p" )
125132 test ! -f " $BACKUP_FILE " \
126133 && echoerr " pg_backup restore: source backup archive file '$BACKUP_FILE_OR_DIR /base.tar.*' does not exist" && exit 1
127134 else
@@ -141,23 +148,34 @@ elif test "${1:-}" = "restore"; then
141148 exit 1
142149 fi
143150
144- echo " Расшифровываем и распаковываем архив '$BACKUP_FILE ' в папку '$PG_DATA_DIR '"
151+ if test " $GPG_ENCRYPT " = 0; then
152+ echo " Распаковываем архив '$BACKUP_FILE ' в папку '$PG_DATA_DIR '"
153+ GPG_COMMAND=" cat"
154+ else
155+ echo " Расшифровываем и распаковываем архив '$BACKUP_FILE ' в папку '$PG_DATA_DIR '"
156+ GPG_COMMAND=" gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch"
157+ fi
158+ # посмотреть прогресс выполнения процесса pv: sudo pv -d PID
145159 pv -trebp $BACKUP_FILE \
146- | gpg --decrypt --passphrase= $GPG_PASSPHRASE --batch \
160+ | $GPG_COMMAND \
147161 | tar -xf - --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_DIR
148162
149163 if test -d " $BACKUP_FILE_OR_DIR " ; then
150164 WAL_FILE=" $BACKUP_FILE_OR_DIR /pg_wal.$BACKUP_FILE_EXT "
151165 test ! -f " $WAL_FILE " && echoerr " Файл '$WAL_FILE ' не найден" && exit 1
152- echo " Расшифровываем и распаковываем архив '$WAL_FILE ' в папку '$PG_DATA_DIR /pg_wal'"
166+ if test " $GPG_ENCRYPT " = 0; then
167+ echo " Распаковываем архив '$WAL_FILE ' в папку '$PG_DATA_DIR /pg_wal'"
168+ else
169+ echo " Расшифровываем и распаковываем архив '$WAL_FILE ' в папку '$PG_DATA_DIR /pg_wal'"
170+ fi
153171 pv -trebp $WAL_FILE \
154- | gpg --decrypt --passphrase= $GPG_PASSPHRASE --batch \
172+ | $GPG_COMMAND \
155173 | tar -xf - --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_DIR /pg_wal
156174 fi
157175
158176 echo " Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)"
159177 # https://www.google.com/search?q=Linux+curly+brace+expansion+documentation
160- rm -f -r -v $PG_DATA_DIR /{* .{signal,{backup,old}{,.* }},log/* }
178+ rm -f -r -v $PG_DATA_DIR /{* .{signal,{backup,old}{,.* }},log/* , * ~ }
161179
162180 TIME_END=$( date +%s) # время в Unixtime
163181 TIME_ELAPSED=$( elapsed $TIME_START $TIME_END )
@@ -180,7 +198,7 @@ elif test "${1:-}" = "restore"; then
180198elif test " ${1:- } " = " validate" ; then
181199 echo " Получаем название предпоследнего или последнего файла с архивом резервной копии (сортировка по дате модификации)"
182200 BACKUP_FILE=$( find $BACKUP_DIR -maxdepth 2 -type f \( -name " *.pg_backup.tar.*" -o -path " *.pg_backup/base.tar.*" \) \
183- -printf " %T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2)
201+ ! -name " *.log " -printf " %T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2)
184202 test -z " $BACKUP_FILE " && echoerr " pg_backup validate: no backup archive file found in directory '$BACKUP_DIR '" && exit 1
185203 echo " pg_backup validate: archive file '$BACKUP_FILE ' selected"
186204
@@ -208,20 +226,36 @@ elif test "${1:-}" = "validate"; then
208226 stat -c " %a" $PG_DATA_TEST_DIR | grep -qP ' ^7[05]0$' \
209227 || (echoerr " pg_backup validate: directory '$PG_DATA_TEST_DIR ' permission must be 750 or 700" && exit 1)
210228
211- echo " Расшифровываем и распаковываем архив '$BACKUP_FILE ' в папку '$PG_DATA_TEST_DIR '"
212- gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \
213- | tar -xf - --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_TEST_DIR
229+ if test " $GPG_ENCRYPT " = 0; then
230+ echo " Распаковываем архив '$BACKUP_FILE ' в папку '$PG_DATA_TEST_DIR '"
231+ tar -xf $BACKUP_FILE --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_TEST_DIR \
232+ 2> $LOG_FILE_PREFIX .tar.stderr.log
233+ else
234+ echo " Расшифровываем и распаковываем архив '$BACKUP_FILE ' в папку '$PG_DATA_TEST_DIR '"
235+ gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \
236+ 2> $LOG_FILE_PREFIX .gpg.stderr.log \
237+ | tar -xf - --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_TEST_DIR \
238+ 2> $LOG_FILE_PREFIX .tar.stderr.log
239+ fi
214240
215241 BACKUP_BASE_DIR=$( echo " $BACKUP_FILE " | grep -qP ' \.pg_backup/base\.tar\.' && dirname " $BACKUP_FILE " || true)
216242 if test ! -z " $BACKUP_BASE_DIR " ; then
217243 echo " Копируем '$BACKUP_BASE_DIR /backup_manifest' в папку '$PG_DATA_TEST_DIR '"
218244 cp $BACKUP_BASE_DIR /backup_manifest $PG_DATA_TEST_DIR
219245
220- FILE=" $BACKUP_BASE_DIR /pg_wal.$BACKUP_FILE_EXT "
221- test ! -f " $FILE " && echoerr " Файл '$FILE ' не найден" && exit 1
222- echo " Расшифровываем и распаковываем архив '$FILE ' в папку '$PG_DATA_TEST_DIR /pg_wal'"
223- gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $FILE \
224- | tar -xf - --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_TEST_DIR /pg_wal
246+ WAL_FILE=" $BACKUP_BASE_DIR /pg_wal.$BACKUP_FILE_EXT "
247+ test ! -f " $WAL_FILE " && echoerr " Файл '$WAL_FILE ' не найден" && exit 1
248+ if test " $GPG_ENCRYPT " = 0; then
249+ echo " Распаковываем архив '$WAL_FILE ' в папку '$PG_DATA_TEST_DIR /pg_wal'"
250+ tar -xf $WAL_FILE --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_TEST_DIR /pg_wal \
251+ 2> $LOG_FILE_PREFIX .tar.stderr.log
252+ else
253+ echo " Расшифровываем и распаковываем архив '$WAL_FILE ' в папку '$PG_DATA_TEST_DIR /pg_wal'"
254+ gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $WAL_FILE \
255+ 2> $LOG_FILE_PREFIX .gpg.stderr.log \
256+ | tar -xf - --use-compress-program=" $COMPRESS_PROGRAM " --directory=$PG_DATA_TEST_DIR /pg_wal \
257+ 2> $LOG_FILE_PREFIX .tar.stderr.log
258+ fi
225259 fi
226260
227261 DIR_SIZE=$( du -sh " $PG_DATA_TEST_DIR " | grep -oP ' ^\S+' )
@@ -235,7 +269,7 @@ elif test "${1:-}" = "validate"; then
235269
236270 echo " Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)"
237271 # https://www.google.com/search?q=Linux+curly+brace+expansion+documentation
238- rm -f -r -v $PG_DATA_TEST_DIR /{* .{signal,{backup,old}{,.* }},log/* }
272+ rm -f -r -v $PG_DATA_TEST_DIR /{* .{signal,{backup,old}{,.* }},log/* , * ~ }
239273
240274 echo " Разрешаем локальному пользователю postgres аутентифицироваться методом peer"
241275 sed -i ' 1i local all postgres peer' $PG_DATA_TEST_DIR /pg_hba.conf # добавляем строчку в начало файла
@@ -267,13 +301,17 @@ elif test "${1:-}" = "validate"; then
267301 echo " pg_backup validate: data checksum failures: 0"
268302 fi
269303
270- echo " Проверяем логическую целостность таблиц и индексов (amcheck)"
271- if $PG_BIN_DIR /pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* --rootdescend --on-error-stop \
272- 1> $LOG_FILE_PREFIX .pg_amcheck.stdout.log \
273- 2> $LOG_FILE_PREFIX .pg_amcheck.stderr.log ; then
274- echo " pg_backup validate: amcheck OK"
304+ if test " $PG_AMCHECK_VALIDATE " = 0; then
305+ echowarn " Проверка логической целостности таблиц и индексов (amcheck) отключена"
275306 else
276- echowarn " pg_backup validate: amcheck ERROR"
307+ echo " Проверяем логическую целостность таблиц и индексов (amcheck)"
308+ if $PG_BIN_DIR /pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* --rootdescend --on-error-stop \
309+ 1> $LOG_FILE_PREFIX .pg_amcheck.stdout.log \
310+ 2> $LOG_FILE_PREFIX .pg_amcheck.stderr.log ; then
311+ echo " pg_backup validate: amcheck OK"
312+ else
313+ echowarn " pg_backup validate: amcheck ERROR"
314+ fi
277315 fi
278316
279317 echo " Останавливаем сервер СУБД"
@@ -313,8 +351,9 @@ elif test "${1:-}" = "validate"; then
313351 echosucc " pg_backup validate: success, duration: $TIME_ELAPSED (day:hh:mm:ss)"
314352 exit 0
315353
316- elif test -n " ${1:- } " ; then
317- echoerr " pg_backup: unknown first parameter '${1:- } '"
354+ elif test " ${1:- } " ! = " create" ; then
355+ echoinfo " Usage: $0 COMMAND"
356+ echo " COMMAND - one of: create, validate, restore"
318357 exit 2
319358fi
320359
@@ -323,7 +362,7 @@ echoinfo "pg_backup: creating started"
323362BASE_NAME=${BACKUP_DIR} /$( date +%Y-%m-%d.%H%M%S) .$( hostname) .pg_backup
324363COMPRESS_THREADS=$( echo " $( nproc) / 2.5 + 1" | bc)
325364
326- # для многопоточного режима используется максимальная степень сжатия 5, которая получена опытным путём
365+ # для многопоточного режима используется максимальная степень сжатия 5, которая была получена опытным путём
327366# это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск, с учётом нагрузки другими процессами
328367COMPRESS_LEVEL=$COMPRESS_THREADS
329368test " $COMPRESS_LEVEL " -gt 5 && $COMPRESS_LEVEL =5
@@ -337,47 +376,66 @@ IS_BACKUP_WAL=$(psql --username=$PG_USERNAME --no-password --dbname=postgres --q
337376
338377if test " $IS_BACKUP_WAL " = " f" ; then
339378 echo ' Создаём физическую резервную копию (без WAL файлов)'
340- FILE=" ${BASE_NAME} .tar.zst.gpg"
341- ${PG_BIN_DIR} /pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \
342- | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} \
343- | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE
379+ COMMAND=" ${PG_BIN_DIR} /pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=-"
380+ if test " $GPG_ENCRYPT " = 0; then
381+ FILE=" ${BASE_NAME} .tar.zst"
382+ ($COMMAND | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} -o $FILE ) 2> $BASE_NAME .stderr.log
383+ else
384+ FILE=" ${BASE_NAME} .tar.zst.gpg"
385+ ($COMMAND | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} \
386+ | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE ) 2> $BASE_NAME .stderr.log
387+ fi
344388 SIZE=$( du -sh " $FILE " | grep -oP ' ^\S+' )
345389 echoinfo " Создан файл '$FILE ' (size: $SIZE )"
346390else
347391 echo ' Создаём физическую резервную копию (с WAL файлами)'
348392 PG_MAJOR_VER=$( echo " $PG_BIN_DIR " | grep -oP ' \-\K\d+(?=/)' )
393+ OPT_COMPRESS=1 # gzip support only
349394 if test " $PG_MAJOR_VER " -ge 15; then
350395 # в библиотеке libzstd многопоточность поддерживается с версии 1.5.0
351396 LIBZSTD_VER=$( rpm -q libzstd | grep -oP ' ^libzstd-\K\d+\.\d+' )
352397 test -z " $LIBZSTD_VER " && echoerr " pg_backup: cannot get libzstd version, it is installed?" && exit 1
353398 OPT_COMPRESS=" server-zstd:level=1"
354399 test $( echo " $LIBZSTD_VER >= 1.5" | bc -l) = 1 && OPT_COMPRESS=" server-zstd:level=${COMPRESS_LEVEL} ,workers=${COMPRESS_THREADS} "
355- else
356- OPT_COMPRESS=1 # gzip support only
357400 fi
401+ mkdir -p ${BASE_NAME}
358402 ${PG_BIN_DIR} /pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \
359- --pgdata=${BASE_NAME}
403+ --pgdata=${BASE_NAME} \
404+ 2> $BASE_NAME .stderr.log
360405
361406 FILES=" ${BASE_NAME} /base.tar ${BASE_NAME} /pg_wal.tar"
362407 for FILE in $FILES ; do
363408 if test -f " $FILE " ; then
364- echo " Сжимаем и шифруем '$FILE '"
365- if test " $PG_MAJOR_VER " -ge 15; then
366- zstd -c -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \
367- | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE .zst.gpg
409+ if test " $GPG_ENCRYPT " = 0; then
410+ echo " Сжимаем '$FILE '"
411+ if test " $PG_MAJOR_VER " -ge 15; then
412+ zstd -c -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE -o $FILE .zst 2> $BASE_NAME .stderr.log
413+ else
414+ pigz -c -q -p ${COMPRESS_THREADS} -${COMPRESS_LEVEL} -o $FILE .gz 2> $BASE_NAME .stderr.log
415+ fi
368416 else
369- pigz -c -q -p ${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \
370- | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE .gz.gpg
417+ echo " Сжимаем и шифруем '$FILE '"
418+ if test " $PG_MAJOR_VER " -ge 15; then
419+ (zstd -c -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \
420+ | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE .zst.gpg) 2> $BASE_NAME .stderr.log
421+ else
422+ (pigz -c -q -p ${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \
423+ | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE .gz.gpg) 2> $BASE_NAME .stderr.log
424+ fi
371425 fi
372426 rm -f $FILE
373427 elif test -f " $FILE .zst" ; then
374- echo " Шифруем '$FILE .zst'"
375- gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE .zst
376- rm -f $FILE .zst
428+ if test " $GPG_ENCRYPT " = 1; then
429+ echo " Шифруем '$FILE .zst'"
430+ gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE .zst 2> $BASE_NAME .stderr.log
431+ rm -f $FILE .zst
432+ fi
377433 elif test -f " $FILE .gz" ; then
378- echo " Шифруем '$FILE .gz'"
379- gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE .gz
380- rm -f $FILE .gz
434+ if test " $GPG_ENCRYPT " = 1; then
435+ echo " Шифруем '$FILE .gz'"
436+ gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE .gz 2> $BASE_NAME .stderr.log
437+ rm -f $FILE .gz
438+ fi
381439 else
382440 echoerr " Файл '$FILE ' или '$FILE .zst' или '$FILE .gz' не найден" && exit 1
383441 fi
392450TIME_END=$( date +%s) # время в Unixtime
393451TIME_ELAPSED=$( elapsed $TIME_START $TIME_END )
394452
395- echosucc " pg_backup: created successfully , duration: $TIME_ELAPSED (day:hh:mm:ss)"
453+ echosucc " pg_backup: created, duration: $TIME_ELAPSED (day:hh:mm:ss)"
396454
397455# -----------------------------------------------------------------------------------------------------------------------
398456# удаляем архивные резервные копии старше N дней (папки и файлы рекурсивно)
399457echo " pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days"
400458find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete
459+ echosucc " pg_backup: old backup files deleted"
401460
461+ # -----------------------------------------------------------------------------------------------------------------------
402462# удаляем архивные WAL файлы старше N дней (сортировка по дате модификации)
403463echo " pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days"
404464WAL_OLD_FILE=$( find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f ! -size 0 \
405465 ! -name " *.history" ! -name " *.history.*" -printf " %T@ %f\n" \
406466 | sort -n | tail -1 | cut -d" " -f2)
407467if test -z " ${WAL_OLD_FILE} " ; then
408- echowarn " pg_backup: WAL old file is not found"
468+ echowarn " pg_backup: old WAL file is not found"
409469else
410470 echo " pg_backup: WAL old file is ${WAL_OLD_FILE} "
411471 WAL_OLD_FILE_EXT=$( echo " ${WAL_OLD_FILE} " | grep -oP ' \.[a-z\d]+$' ) # compressed files support (.gz, .zst, .lz4)
412472
413473 BEFORE_WAL_DIR_SIZE=$( du -sh " ${WAL_DIR} " | grep -oP ' ^\S+' )
414- ${PG_BIN_DIR} /pg_archivecleanup -x " ${WAL_OLD_FILE_EXT} " " ${WAL_DIR} " " ${WAL_OLD_FILE} "
474+ ${PG_BIN_DIR} /pg_archivecleanup -x " ${WAL_OLD_FILE_EXT} " " ${WAL_DIR} " " ${WAL_OLD_FILE} " 2> ${WAL_DIR} /pg_archivecleanup.stderr.log
415475
416476 AFTER_WAL_DIR_SIZE=$( du -sh " ${WAL_DIR} " | grep -oP ' ^\S+' )
417477 echo " pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)"
478+ echosucc " pg_backup: old WAL files deleted"
418479fi
419480
420- echosucc " pg_backup: done "
481+ exit 0
0 commit comments