Skip to content

Commit 3be29eb

Browse files
authored
Update pg_backup.sh
PG_AMCHECK_VALIDATE, GPG_ENCODE support extended error log to files
1 parent 1e7abbd commit 3be29eb

File tree

1 file changed

+113
-52
lines changed

1 file changed

+113
-52
lines changed

pg_backup/pg_backup.sh

Lines changed: 113 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ Reset='\e[0m'
2828
# colored messages
2929
echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } # ошибки
3030
echowarn() { 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
3536
elapsed() {
@@ -59,8 +60,14 @@ if test "$(whoami)" != "postgres"; then
5960
elif ! 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
6572
elif 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
180198
elif 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
319358
fi
320359

@@ -323,7 +362,7 @@ echoinfo "pg_backup: creating started"
323362
BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname).pg_backup
324363
COMPRESS_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc)
325364

326-
# для многопоточного режима используется максимальная степень сжатия 5, которая получена опытным путём
365+
# для многопоточного режима используется максимальная степень сжатия 5, которая была получена опытным путём
327366
# это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск, с учётом нагрузки другими процессами
328367
COMPRESS_LEVEL=$COMPRESS_THREADS
329368
test "$COMPRESS_LEVEL" -gt 5 && $COMPRESS_LEVEL=5
@@ -337,47 +376,66 @@ IS_BACKUP_WAL=$(psql --username=$PG_USERNAME --no-password --dbname=postgres --q
337376

338377
if 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)"
346390
else
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
@@ -392,29 +450,32 @@ fi
392450
TIME_END=$(date +%s) # время в Unixtime
393451
TIME_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 дней (папки и файлы рекурсивно)
399457
echo "pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days"
400458
find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete
459+
echosucc "pg_backup: old backup files deleted"
401460

461+
# -----------------------------------------------------------------------------------------------------------------------
402462
# удаляем архивные WAL файлы старше N дней (сортировка по дате модификации)
403463
echo "pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days"
404464
WAL_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)
407467
if 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"
409469
else
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"
418479
fi
419480

420-
echosucc "pg_backup: done"
481+
exit 0

0 commit comments

Comments
 (0)