Advertisement
Guest User

Untitled

a guest
Mar 24th, 2016
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.16 KB | None | 0 0
  1. #!/usr/bin/env bash
  2. set -euf -o pipefail
  3.  
  4. # Set defaults, can be overriden with config_file (-f)
  5. host="127.0.0.1"
  6. port="3306"
  7. user=
  8. password=
  9. excluded_dbs= # must be comma-delimeted, eg "mysql,test,innodb"
  10. included_dbs=
  11. rotate=false # true enables weekly/monthly rotation (copy)
  12. do_monthly="01" # 01 to 31, 0 to disable monthly
  13. do_weekly="6" # day of week 1-7, 1 is Monday, 0 disables weekly
  14. do_latest=true
  15. bucket=
  16. region="us-east-1"
  17. s3_daily_prefix="daily"
  18. s3_weekly_prefix="weekly"
  19. s3_monthly_prefix="monthly"
  20. s3_latest_prefix="latest"
  21. slave=false
  22. rds_slave=false
  23. dry_run=false
  24.  
  25. # don't override these
  26. repl_stopped=0
  27.  
  28. usage(){
  29. echo "Usage: $0 args
  30. -f PATH config file to use rather than specifying CLI arguments
  31. -u USER mysql username
  32. -p PWD mysql password
  33. -H HOST mysql host (default: 127.0.0.1)
  34. -P PORT mysql port (default: 3306)
  35. -e DB database to exclude, can be called multiple times,
  36. or called once with comma-delimited list (no spaces)
  37. will backup all databases except db(s) specified by this option
  38. (information_schema and performance_schema are _always_ excluded)
  39. cannot be used with -i
  40. -i DB database to include, can be called multiple times
  41. or called once with comma-delimited list (no spaces)
  42. will only backup specified db(s), cannot be used with -e
  43. -s this is a slave, will pause replication during backup
  44. -S this is an RDS read-replica, will stop replication during backup
  45. -b BKT S3 bucket name
  46. -R RGN AWS Region (default: us-east-1)
  47. -r enable weekly/monthly rotation, files will be copied to
  48. weekly/monthly S3 prefixes if this option is enabled.
  49. Note: UTC is used when calculating day of week and month
  50. -m INT day of month to do monthly backup (01 to 31) (default: 01)
  51. use 0 to disable monthly backups (only relevant if -R specified)
  52. -w INT day of week to do weekly backups (1-7, 1 is Monday) (default: 6)
  53. use 0 to disable weekly backups (only relevant if -R specified)
  54. -d dry-run, explain only, will not pause replication or perform backups" >&2
  55. exit 1
  56. }
  57.  
  58. die() {
  59. if [ -n "$1" ]; then echo "Error: $1" >&2; fi
  60. exit 1
  61. }
  62.  
  63. mysql_cmd() {
  64. mysql --defaults-file="$cnf_file" --batch --skip-column-names -e "$1"
  65. }
  66.  
  67. # calculate number of days in month; taken from automysqlbackup
  68. # $1 = month, $2 = year
  69. days_in_month() {
  70. m="$1"; y="$2"; a=$(( 30+(m+m/8)%2 ))
  71. (( m==2 )) && a=$((a-2))
  72. (( m==2 && y%4==0 && ( y<100 || y%100>0 || y%400==0) )) && a=$((a+1))
  73. printf '%d' $a
  74. }
  75.  
  76. # Cleanup - ensure replication restarted and remove temp file
  77. cleanup() {
  78. if [ "$slave" = true ] && [ "$repl_stopped" -eq "1" ]; then
  79. if [ "$dry_run" = true ]; then
  80. echo "execute 'START SLAVE SQL_THREAD'"
  81. else
  82. mysql_cmd 'START SLAVE SQL_THREAD'
  83. fi
  84. fi
  85. if [ "$rds_slave" = true ] && [ "$repl_stopped" -eq "1" ]; then
  86. if [ "$dry_run" = true ]; then
  87. echo "execute 'call mysql.rds_start_replication()'"
  88. else
  89. mysql_cmd 'call mysql.rds_start_replication()'
  90. fi
  91. fi
  92. rm -f "$cnf_file"
  93. }
  94.  
  95. while getopts ":f:u:p:H:P:e:i:sSb:R:rm:w:ldh" opt; do
  96. case $opt in
  97. f) config_file=$OPTARG;;
  98. u) user=$OPTARG;;
  99. p) password=$OPTARG;;
  100. H) host=$OPTARG;;
  101. P) port=$OPTARG;;
  102. e)
  103. if [ -n "$excluded_dbs" ]; then
  104. excluded_dbs="$excluded_dbs,$OPTARG"
  105. else
  106. excluded_dbs=$OPTARG
  107. fi
  108. ;;
  109. i)
  110. if [ -n "$included_dbs" ]; then
  111. included_dbs="$included_dbs,$OPTARG"
  112. else
  113. included_dbs=$OPTARG
  114. fi
  115. ;;
  116. s) slave=true;;
  117. S) rds_slave=true;;
  118. b) bucket=$OPTARG;;
  119. R) region=$OPTARG;;
  120. r) rotate=true;;
  121. m) do_monthly=$OPTARG;;
  122. w) do_weekly=$OPTARG;;
  123. l) do_latest=false;;
  124. d) dry_run=true;;
  125. h) usage;;
  126. \?)
  127. echo "Invalid option: -$OPTARG" >&2
  128. exit 1
  129. ;;
  130. :)
  131. echo "Option -$OPTARG requires an argument." >&2
  132. exit 1
  133. ;;
  134. esac
  135. done
  136.  
  137. if [ -n "$config_file" ]; then
  138. if [ ! -f "$config_file" ]; then die "config file '$config_file' does not exist"; fi
  139. if [ ! -r "$config_file" ]; then die "unable to access config file '$config_file'"; fi
  140. # shellcheck source=/dev/null
  141. source "$config_file"
  142. fi
  143.  
  144. # basic input validation
  145. if [ -z "$host" ]; then die "host is required"; fi
  146. if [ -z "$user" ]; then die "username is required"; fi
  147. if [ -z "$bucket" ]; then die "bucket is required"; fi
  148. if [ "$slave" = true ] && [ "$rds_slave" = true ]; then
  149. die "slave option must be either slave or RDS slave, not both"
  150. fi
  151. if [ -n "$included_dbs" ] && [ -n "$excluded_dbs" ]; then
  152. die "specifying included *and* excluded databases is not supported"
  153. fi
  154. if [[ ! $do_weekly =~ ^[0-7]$ ]]; then die "invalid weekday"; fi
  155. if [[ ! $do_monthly =~ ^(0|0[0-9]|[12][0-9]|3[01])$ ]]; then die "invalid month day: $do_monthly"; fi
  156.  
  157. if [ "$dry_run" = true ]; then echo "Dry run enabled"; fi
  158.  
  159. # write temporary defaults-file
  160. cnf_file=$(mktemp -t "$(basename "$0")".XXXXXXXXXX)
  161. cat << EOF > $cnf_file
  162. [client]
  163. user=$user
  164. password=$password
  165. host=$host
  166. port=$port
  167. EOF
  168.  
  169. # cleanup on any exit (restart replication, remove temp file, etc.)
  170. trap cleanup ERR INT TERM EXIT
  171.  
  172. stamp=$(date -u +%Y%m%dT%H%MZ) # UTC ISO-8601
  173. s3_stamp_match='????????T????Z' # must be able to match stamp above
  174. date_day_of_week=$(date -u +%u)
  175. date_day_of_month=$(date -u +%d)
  176. date_year=$(date -u +%Y)
  177. last_day_of_month=$(days_in_month "$date_day_of_month" "$date_year")
  178.  
  179. # get array of databases to backup
  180. if [ -n "$included_dbs" ]; then
  181. OIFS=$IFS
  182. IFS=','
  183. read -r -a databases <<< "$included_dbs"
  184. IFS=$OIFS
  185. all_dbs=($(mysql_cmd 'SHOW DATABASES'))
  186. for db in "${databases[@]}"; do
  187. if [[ ! " ${all_dbs[@]} " =~ " ${db} " ]]; then
  188. die "database $db not found"
  189. fi
  190. done
  191. else
  192. skip_dbs="information_schema,performance_schema"
  193. if [ -n "$excluded_dbs" ]; then skip_dbs="$skip_dbs|$excluded_dbs"; fi
  194. # replace comma with | and add $ anchor to end of each db name for grep match
  195. databases=($(mysql_cmd 'SHOW DATABASES' | grep -Ev "(${skip_dbs//,/$|}$)"))
  196. fi
  197.  
  198. # pause replication
  199. if [ "$slave" = true ]; then
  200. if [ "$dry_run" = true ]; then
  201. echo "execute 'STOP SLAVE SQL_THREAD'"
  202. else
  203. mysql_cmd 'STOP SLAVE SQL_THREAD'
  204. fi
  205. repl_stopped=1
  206. fi
  207.  
  208. if [ "$rds_slave" = true ]; then
  209. if [ "$dry_run" = true ]; then
  210. echo "execute 'call mysql.rds_stop_replication()'"
  211. else
  212. mysql_cmd 'call mysql.rds_stop_replication()'
  213. fi
  214. repl_stopped=1
  215. fi
  216.  
  217. # do the needful
  218. for db in "${databases[@]}"; do
  219. fname="${db}_${stamp}.sql.gz"
  220. s3_path="s3://$bucket/$s3_daily_prefix/$db/$fname"
  221.  
  222. echo "Dumping $db to $s3_path"
  223. if [ "$dry_run" != true ]; then
  224. mysqldump --defaults-file="$cnf_file" --single-transaction "$db" | gzip | aws s3 cp - "$s3_path" --region "$region"
  225. fi
  226.  
  227. if [ "$rotate" = true ]; then
  228. # weekly
  229. if (( do_weekly == date_day_of_week )); then
  230. s3_weekly_path="s3://$bucket/$s3_weekly_prefix/$db/$fname"
  231. if [ "$dry_run" = true ]; then
  232. echo "aws s3 cp \"$s3_path\" \"$s3_weekly_path\" --region \"$region\""
  233. else
  234. aws s3 cp "$s3_path" "$s3_weekly_path" --region "$region"
  235. fi
  236. fi
  237.  
  238. # monthly
  239. if (( date_day_of_month == do_monthly || date_day_of_month == last_day_of_month && last_day_of_month < do_monthly )); then
  240. s3_monthly_path="s3://$bucket/$s3_monthly_prefix/$db/$fname"
  241. if [ "$dry_run" = true ]; then
  242. echo "aws s3 cp \"$s3_path\" \"$s3_monthly_path\" --region \"$region\""
  243. else
  244. aws s3 cp "$s3_path" "$s3_monthly_path" --region "$region"
  245. fi
  246. fi
  247.  
  248. # latest
  249. if [ "$do_latest" = true ]; then
  250. if [ "$dry_run" = true ]; then
  251. echo "aws s3 cp $s3_path s3://$bucket/$s3_latest_prefix/$fname"
  252. echo "aws s3 rm s3://$bucket/$s3_latest_prefix/ --recursive --exclude=\"*\" --include=\"${db}_${s3_stamp_match}.sql.gz\" --exclude=\"$fname\""
  253. else
  254. aws s3 cp "$s3_path" "s3://$bucket/$s3_latest_prefix/$fname"
  255. # delete all files like "db_name_YYYYMMDDTHHMMZ.sql.gz" except for the one just copied
  256. aws s3 rm "s3://$bucket/$s3_latest_prefix/" --recursive --exclude="*" --include="${db}_${s3_stamp_match}.sql.gz" --exclude="$fname"
  257. fi
  258. fi
  259. fi
  260. done
  261.  
  262. echo "Done. Completed at $(date -u +%Y-%m-%dT%H:%M:%SZ)"
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement