├── .github └── FUNDING.yml ├── LICENSE ├── README.md └── ddt.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['paypal.me/sshto'] 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ivan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DDT 2 | 3 | Downloads postgresql dumps from servers via rsync over ssh, tests them by restoring on a test DB server.
4 | Sends results to email via mutt(i'm using it cuz it add attachments).
5 | 6 | Servers addresses, dump folders, Dump search patterns and test DB names are stored in dbases array in form of a table:
7 |
 8 | dbases=(
 9 | #-----------------------+-------------------+-----------------------------+--------------------------------------+
10 | #    Ssh alias(addr)    |Dump folder(bkpath)| Dump search pattern(dbname) | Test DB name(dbtest) Must be unique! |
11 | #-----------------------+-------------------+-----------------------------+--------------------------------------+
12 |       'moscow'             '/backup'           'data_db_%d.%m.%Y'                   'moscow_data_prod_db'
13 |       'rybinsk'            '/backup/new'       '%Y%m%d_db_data'                     'rybinsk_data_prod_db'
14 |       'yaroslavl'          '/dumps'            'data_db%Y'                          'yar_data_prod_db'
15 | #-----------------------+-------------------+-----------------------------+--------------------------------------+
16 | ); N=${#dbases[*]}; C=4
17 | 
18 | 19 | Have fun)! 20 | 21 | 22 | [![Twitter Follow](https://img.shields.io/twitter/follow/Vaniacer?style=social)](https://twitter.com/Vaniacer) 23 | [![paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/sshto?locale.x=en_US) Don't hold yourself, buy me a beer) 24 | -------------------------------------------------------------------------------- /ddt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | dbases=( 3 | #-----------------+---------------------------+---------------------------+-------------+---------------------------+ 4 | # Ssh alias(addr) | Dump folder(bkpath) |Dump search pattern(dbname)|Dump ext\type|Unique test DB name(dbtest)| 5 | #-----------------+---------------------------+---------------------------+-------------+---------------------------+ 6 | 'moscow' '/backup/%d-%m' 'data_db_%d.%m.%Y' 'gz' 'moscow_data_prod_db' 7 | 'rybinsk' '/backup/new' '%Y%m%d_db_data' 'dump' 'rybinsk_data_prod_db' 8 | 'yaroslavl' '/dumps' 'data_db%Y' 'dmp' 'yar_data_prod_db' 9 | #-----------------+---------------------------+---------------------------+-------------+---------------------------+ 10 | ); N=${#dbases[*]}; C=5 11 | 12 | dmpdir=~/dumps # Dir to store dumps 13 | dlderr=~/logs/dlderr # Download errors file 14 | dbserr=~/logs/dbserr # DB test errors file 15 | subjct="DDT" # Email options - subject 16 | mailto="user@pisem.net" # Email options - address 17 | mydate=$(date +'%d.%m.%Y') # Date to search dumps 18 | hasher=sha1sum # Hash algorithm 19 | dbhost=192.168.0.1 # DB server to test dump 20 | dbport=5432 # DB server port 21 | dbuser=dbuser # User of test DB server 22 | dbpass=password # DB user password 23 | dbconf="-U $dbuser -h $dbhost -p $dbport" # DB connection parameters 24 | slimit= # Rsync speed limit 25 | dmeror=('' 26 | ' ___ __ __ ___ _ \n' 27 | '| \ \/ | _ \ ___ __| |_ \n' 28 | '| || ||\/| ||_) / __/__| _ \\\n' 29 | '| || || | | __/\__ \_ \| | |\n' 30 | '|___/_|__|_||__ |___/__/| |_|\n' 31 | '| ____| _ \ _ \/ _ \ _ \| |\n' 32 | '| _|| |_) ||_) || | ||_) | |\n' 33 | '| |__| _ < _ < |_| | _ <|_|\n' 34 | '|_____|| \_\| \_\___/_| \_(_)\n') 35 | 36 | dleror=('' 37 | ' ____ _____ ___ _ ____ \n' 38 | '| _ \/ __\ \ / / \ | |/ ___|\n' 39 | '| |_) \___ \ V /| \| | | \n' 40 | '| _ < ___) | | | |\ | |___ \n' 41 | '|_|_\_\____/|_| |_|_\_|\____|\n' 42 | '| ____| _ \ _ \/ _ \ _ \| |\n' 43 | '| _|| |_) ||_) || | ||_) | |\n' 44 | '| |__| _ < _ < |_| | _ <|_|\n' 45 | '|_____|| \_\| \_\___/_| \_(_)\n') 46 | 47 | dberor=('' 48 | ' ____ ____ _____ _ \n' 49 | '| _ \| __ )_ _|_ ___| |_ \n' 50 | '| | | | _ \ | / _ \/ __| __|\n' 51 | '| |_| | |_) || | __/\__ \ |_ \n' 52 | '|____/|____/_|_|___||___/\__|\n' 53 | '| ____| _ \ _ \/ _ \ _ \| |\n' 54 | '| _|| |_) ||_) || | ||_) | |\n' 55 | '| |__| _ < _ < |_| | _ <|_|\n' 56 | '|_____|| \_\| \_\___/_| \_(_)\n') 57 | 58 | download () { 59 | rerr= 60 | for j in {0..9}; { 61 | rerr=$(rsync --bwlimit=$slimit -Pqz $addr:"$bkpath/$dump" "$dmpdir/$localdump" 2>&1 > /dev/null) \ 62 | && { printf "\nDownload complete.\n"; return 0; } \ 63 | || sleep 5 64 | } 65 | printf "${dleror[*]}\n$rerr"; return 1 66 | } 67 | 68 | check () { 69 | for ((i=0; i<$N; i+=$C)); do printf '\n----------------------------------------------\n' 70 | 71 | read addr bkpath dbname ext dbtest <<< ${dbases[@]:$i:$C} 72 | # Restrict connections to test DB 73 | # Terminate connections to test DB if PostgreSQL ver. <= 9.1 change 'pid' to 'procpid' 74 | # Then drop test DB and create new test DB 75 | dbterm=" 76 | ALTER DATABASE $dbtest ALLOW_CONNECTIONS false; 77 | SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$dbtest';" 78 | 79 | printf "Date\Time:\t%(%d.%m.%Y %R)T\n" 80 | printf "DBServer:\t$addr\n" 81 | 82 | bkpath="$(date +${bkpath})" 83 | dump=( $(ssh $addr ls -t "$bkpath" | grep $(date +${dbname}).*.$ext) ) \ 84 | || { printf "${dmeror[*]}\nDump not found for the current date($mydate)!\n"; continue; } 85 | localdump=${dbtest}_$mydate.$ext 86 | 87 | size=( $(ssh $addr du -m "$bkpath/$dump") ); size=${size[0]} 88 | hash=( $(ssh $addr $hasher "$bkpath/$dump") ); hash=${hash[0]} 89 | 90 | printf "RemoteFile:\t$bkpath/$dump ($size MB)\n" 91 | printf "RemoteHash:\t$hash\n" 92 | 93 | download || continue 94 | mysize=( $(du -m "$dmpdir/$localdump") ); mysize=${mysize[0]} 95 | myhash=( $($hasher "$dmpdir/$localdump") ); myhash=${myhash[0]} 96 | 97 | [[ $hash = $myhash ]] \ 98 | && { printf " Hash checked!)\n\n"; } \ 99 | || { printf "${dleror[*]}\nLocalhash($myhash) not equal to remotehash($hash)!\n\n"; continue; } 100 | 101 | printf "LocalFile:\t$dmpdir/$localdump ($mysize MB)\n" 102 | printf "LocalHash:\t$myhash\n" 103 | 104 | # Drop test DB connections, drop DB and create DB 105 | psql $dbconf -c "$dbterm" > /dev/null 2>> "$dbserr" 106 | dropdb $dbconf $dbtest > /dev/null 2>> "$dbserr" 107 | createdb $dbconf -O $dbuser $dbtest > /dev/null 2>> "$dbserr" 108 | 109 | # Check dump type and test 110 | terr= 111 | dump="$dmpdir/$localdump" 112 | type=$(file "$dump") 113 | case $type in 114 | *gzip*) terr=$(gunzip -c "$dump" | psql -v ON_ERROR_STOP=1 $dbconf $dbtest 2>&1 > /dev/null);; 115 | *PostgreSQL*) terr=$(pg_restore $dbconf -Oxe -d $dbtest "$dump" 2>&1 > /dev/null);; 116 | esac 117 | [[ $terr ]] && printf "${dberor[*]}\n$terr" 118 | 119 | printf "\nCheck complete!)\n" 120 | 121 | done 122 | } 123 | 124 | exec 5>&1 # Need this to log output in a file by other software 125 | message=$(check|tee /dev/fd/5) # If you don't need to log output then remove 'exec 5>&1' and change message=$(check) 126 | mutt -s "$subjct" "$mailto" <<< "$message" 127 | --------------------------------------------------------------------------------