├── .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 | [](https://twitter.com/Vaniacer)
23 | [](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 |
--------------------------------------------------------------------------------