@@ -19,12 +19,19 @@ label=$default_label
1919# Declarations
2020source_host=
2121source_type=
22- compress_flag=
2322ssh_args=
2423container_tool=${MDL_CONTAINER_TOOL[*]}
2524verbose=false
2625dry_run=false
2726
27+ # Functions
28+ function ssh_wrap() {
29+ echo " ${source_host: +" ssh $ssh_args $source_host $source_sudo \" sh -l -c \\\" " } $1 ${source_host: +" \\\"\" " } "
30+ }
31+ function eval_ssh_wrap() {
32+ eval " $( ssh_wrap " $1 " ) "
33+ }
34+
2835# Help
2936display_help () {
3037 cat << EOF
@@ -140,8 +147,16 @@ shift $((OPTIND - 1))
140147# Calculations
141148#
142149
150+ # Compression extension
151+ compress_ext=' '
152+ case " $compress_arg " in
153+ bzip2) compress_ext=' .bz2' ;;
154+ gzip) compress_ext=' .gz' ;;
155+ xz) compress_ext=' .xz' ;;
156+ esac
157+
143158# Figure out the compression flag to send to tar
144- [[ $compress_arg != none ]] && compress_flag= " -- $compress_arg "
159+ compression_tool= $( calc_compression_tool " $compress_ext " )
145160
146161#
147162# Pre-environment Validation
@@ -177,7 +192,6 @@ if [[ $modules =~ fastdb ]]; then
177192 fi
178193fi
179194
180-
181195# Check necessary utilities
182196cmds=(tar " ${MDL_CONTAINER_TOOL[0]} " )
183197[[ $compress_arg != none ]] && cmds+=(" $compress_arg " )
@@ -241,42 +255,39 @@ for mname in $mnames; do
241255 ping -c 1 " $source_host_server " & > /dev/null && source_host_reachable=true || source_host_reachable=false
242256 fi
243257
244- # If "container", the container must be active in order to dump the database for the "db" module.
258+ # If "container", the containers must be active in order to backup data for their module.
245259 source_db_container=' '
260+ source_moodle_container=' '
246261 if $is_container ; then
247- if [[ " $modules " == * " db " * ]]; then
248- source_db_container_cmd="
249- ${source_host: +" ssh $ssh_args $source_host $source_sudo \" sh -l -c \\\" " }
250- $container_tool ps -f 'label=com.docker.compose.project=$mname ' --format '{{.Names}}' | grep mariadb | head -1
251- ${source_host: +" \\\"\" " }
252- "
253- source_db_container=$( eval " $source_db_container_cmd " )
262+ if [[ " $modules " == * " db " * || $modules =~ fastdb ]]; then
263+ # Find the database container name
264+ source_db_container=$( eval_ssh_wrap " $container_tool ps -f 'label=com.docker.compose.project=$mname ' --format '{{.Names}}' | grep mariadb | head -1" )
254265 [[ -z $source_db_container ]] && echo " ${red} The database container cannot be found. Start the environment to perform a backup." >&2 && exit 1
266+ # Determine db path if not provided (usually won't be for container mode)
267+ if [[ -z $source_db_path && $modules =~ fastdb ]]; then
268+ source_db_path=$( eval_ssh_wrap " $container_tool inspect '$source_db_container ' | jq -r '.[] .Mounts[] | select(.Name != null and (.Name | contains(\" db\" ))) | .Destination'" )
269+ [[ -z $source_db_path ]] && echo " ${red} Could not determine ${ul} db$rmul directory for $ul$mname$rmul !$norm " >&2 && exit 1
270+ fi
255271 fi
256- if [[ $modules =~ data || $modules =~ src || $modules =~ fastdb ]]; then
257- vols_cmd="
258- ${source_host: +" ssh $ssh_args $source_host $source_sudo \" sh -l -c \\\" " }
259- $container_tool volume ls -q --filter 'label=com.docker.compose.project=$mname '
260- ${source_host: +" \\\"\" " }
261- "
262- vols=$( eval " $vols_cmd " )
263- [[ $modules =~ data ]] && source_data_volume=$( grep data <<< " $vols" )
264- [[ $modules =~ src ]] && source_src_volume=$( grep src <<< " $vols" )
265- [[ $modules =~ fastdb ]] && source_db_volume=$( grep db <<< " $vols" )
272+ if [[ $modules =~ data || $modules =~ src ]]; then
273+ # Find the moodle container name
274+ source_moodle_container=$( eval_ssh_wrap " $container_tool ps -f 'label=com.docker.compose.project=$mname ' --format '{{.Names}}' | grep moodle | head -1" )
275+ [[ -z $source_moodle_container ]] && echo " ${red} The Moodle container cannot be found. Start the environment to perform a backup." >&2 && exit 1
276+ # Determine src/data paths if not provided (usually won't be for container mode)
277+ if [[ -z $source_src_path && $modules =~ src ]]; then
278+ source_src_path=$( eval_ssh_wrap " $container_tool inspect '$source_moodle_container ' | jq -r '.[] .Mounts[] | select(.Name != null and (.Name | contains(\" src\" ))) | .Destination'" )
279+ [[ -z $source_src_path ]] && echo " ${red} Could not determine ${ul} src$rmul directory for $ul$mname$rmul !$norm " >&2 && exit 1
280+ fi
281+ if [[ -z $source_data_path && $modules =~ data ]]; then
282+ source_data_path=$( eval_ssh_wrap " $container_tool inspect '$source_moodle_container ' | jq -r '.[] .Mounts[] | select(.Name != null and (.Name | contains(\" data\" ))) | .Destination'" )
283+ [[ -z $source_data_path ]] && echo " ${red} Could not determine ${ul} data$rmul directory for $ul$mname$rmul !$norm " >&2 && exit 1
284+ fi
266285 fi
267286 fi
268287
269288 # Password mask
270289 source_db_password_mask=${source_db_password// ?/* }
271290
272- # Compression extension
273- compress_ext=' '
274- case " $compress_arg " in
275- bzip2) compress_ext=' .bz2' ;;
276- gzip) compress_ext=' .gz' ;;
277- xz) compress_ext=' .xz' ;;
278- esac
279-
280291 # Targets
281292 [[ $modules =~ data ]] && data_target=" $MDL_BACKUP_DIR /${mname} _${label} _data.tar$compress_ext "
282293 [[ $modules =~ src ]] && src_target=" $MDL_BACKUP_DIR /${mname} _${label} _src.tar$compress_ext "
@@ -287,25 +298,24 @@ for mname in $mnames; do
287298 echo -e " $ul$bold$action_word $mname environment$norm "
288299 if $verbose ; then
289300 echo
290- echo " $bold Type:$norm $source_type "
291- [[ -n $source_host ]] && echo " $bold Host:$norm $source_host ($( $source_host_reachable && echo " ${green} reachable" || echo " ${red} unreachable!" ) $norm )"
292- [[ -n $source_src_path ]] && echo " $bold 'src' Path:$norm $source_src_path "
293- [[ -n $source_data_path ]] && echo " $bold 'data' Path:$norm $source_data_path "
294- [[ -n $source_src_volume ]] && echo " $bold 'src' Volume:$norm $source_src_volume "
295- [[ -n $source_data_volume ]] && echo " $bold 'data' Volume:$norm $source_data_volume "
296- [[ -n $source_db_volume ]] && echo " $bold 'db' Volume:$norm $source_db_volume "
297- [[ -n $source_db_container ]] && echo " $bold DB Container:$norm $source_db_container "
298- [[ -n $source_db_name ]] && echo " $bold DB Name:$norm $source_db_name "
299- [[ -n $source_db_username ]] && echo " $bold DB Username:$norm $source_db_username "
300- [[ -n $source_db_password ]] && echo " $bold DB Password:$norm $source_db_password_mask "
301+ echo " $bold Type:$norm $source_type "
302+ [[ -n $source_host ]] && echo " $bold Host:$norm $source_host ($( $source_host_reachable && echo " ${green} reachable" || echo " ${red} unreachable!" ) $norm )"
303+ [[ -n $source_moodle_container ]] && echo " $bold Mdl Container:$norm $source_moodle_container "
304+ [[ -n $source_src_path ]] && echo " $bold 'src' Path:$norm $source_src_path "
305+ [[ -n $source_data_path ]] && echo " $bold 'data' Path:$norm $source_data_path "
306+ [[ -n $source_db_container ]] && echo " $bold DB Container:$norm $source_db_container "
307+ [[ -n $source_db_path ]] && echo " $bold 'db' Path:$norm $source_db_path "
308+ [[ -n $source_db_name ]] && echo " $bold DB Name:$norm $source_db_name "
309+ [[ -n $source_db_username ]] && echo " $bold DB Username:$norm $source_db_username "
310+ [[ -n $source_db_password ]] && echo " $bold DB Password:$norm $source_db_password_mask "
301311 echo
302- [[ -n $label ]] && echo " $bold Label:$norm $label "
303- echo " $bold Modules:$norm $modules "
304- echo " $bold Compress:$norm $compress_arg "
305- [[ -n $src_target ]] && echo " $bold 'src' Path:$norm $src_target "
306- [[ -n $data_target ]] && echo " $bold 'data' Path:$norm $data_target "
307- [[ -n $db_target ]] && echo " $bold DB Path:$norm $db_target "
308- [[ -n $fastdb_target ]] && echo " $bold Fast DB Path:$norm $fastdb_target "
312+ [[ -n $label ]] && echo " $bold Label:$norm $label "
313+ echo " $bold Modules:$norm $modules "
314+ echo " $bold Compress:$norm $compress_arg "
315+ [[ -n $src_target ]] && echo " $bold 'src' Path:$norm $src_target "
316+ [[ -n $data_target ]] && echo " $bold 'data' Path:$norm $data_target "
317+ [[ -n $db_target ]] && echo " $bold DB Path:$norm $db_target "
318+ [[ -n $fastdb_target ]] && echo " $bold Fast DB Path:$norm $fastdb_target "
309319 echo
310320 fi
311321
@@ -315,42 +325,28 @@ for mname in $mnames; do
315325 fi
316326
317327 # MODULE: data
318- # shellcheck disable=SC2034
319- data_cmd=${source_host: +" ssh $ssh_args $source_host $source_sudo \" sh -l -c \\\" " }
320- $is_container && data_cmd=" $data_cmd $container_tool run --rm --name '${mname} _worker_bk_data' -v '$source_data_volume ':/data $MDL_SHELL_IMAGE "
321- data_cmd=" $data_cmd \
322- tar c $compress_flag \
323- --exclude='./trashdir' \
324- --exclude='./temp' \
325- --exclude='./sessions' \
326- --exclude='./localcache' \
327- --exclude='./cache' \
328- --exclude='./moodle-cron.log' \
329- -C $( $is_container && echo /data || echo " $source_data_path " ) . \
328+ $is_container && data_cmd=$( ssh_wrap " $container_tool cp $source_moodle_container :$source_data_path /. -" )
329+ $is_container || data_cmd=$( ssh_wrap " tar c -C '$source_data_path ' ." )
330+ command -v bsdtar & > /dev/null && data_cmd=" $data_cmd | bsdtar cf - \
331+ --exclude localcache \
332+ --exclude cache \
333+ --exclude sessions \
334+ --exclude temp \
335+ --exclude trashdir \
336+ --exclude moodle-cron.log \
337+ @- \
330338 "
331- data_cmd=$data_cmd ${source_host: +" \\\"\" " }
332339
333340 # MODULE: src
341+ $is_container && src_cmd=$( ssh_wrap " $container_tool cp $source_moodle_container :$source_src_path /. -" )
334342 # shellcheck disable=SC2034
335- src_cmd=${source_host: +" ssh $ssh_args $source_host $source_sudo \" sh -l -c \\\" " }
336- $is_container && src_cmd=" $src_cmd $container_tool run --rm --name '${mname} _worker_bk_src' -v '$source_src_volume ':/src $MDL_SHELL_IMAGE "
337- src_cmd=" $src_cmd \
338- tar c $compress_flag \
339- -C $( $is_container && echo /src || echo " $source_src_path " ) . \
340- "
341- src_cmd=$src_cmd ${source_host: +" \\\"\" " }
343+ $is_container || src_cmd=$( ssh_wrap " tar c -C '$source_src_path ' ." )
342344
343345 # MODULE: fastdb
344346 # shellcheck disable=SC2034
345- fastdb_cmd=${source_host: +" ssh $ssh_args $source_host $source_sudo \" sh -l -c \\\" " }
346- $is_container && fastdb_cmd=" $fastdb_cmd $container_tool run --rm --name '${mname} _worker_bk_fastdb' -v '$source_db_volume ':/db $MDL_SHELL_IMAGE "
347- $is_container && fastdb_cmd=" $fastdb_cmd tar c $compress_flag -C /db ."
348- fastdb_cmd=$fastdb_cmd ${source_host: +" \\\"\" " }
347+ $is_container && fastdb_cmd=$( ssh_wrap " $container_tool cp $source_db_container :$source_db_path /. -" )
349348
350349 # MODULE: db
351- # TODO: When piping to compression program, a failed status of mysqldump will be lost.
352- # shellcheck disable=SC2034
353- db_cmd=${source_host: +" ssh $ssh_args $source_host $source_sudo \" sh -l -c \\\" " }
354350 $is_container && db_cmd=" $db_cmd $container_tool exec '$source_db_container '"
355351 db_cmd=" $db_cmd \
356352 mysqldump \
@@ -360,22 +356,21 @@ for mname in $mnames; do
360356 -C -Q -e --create-options \
361357 $source_db_name \
362358 "
363- db_cmd=$db_cmd ${source_host: +" \\\"\" " }
364- [[ $compress_arg != none ]] && db_cmd=" $db_cmd | $compress_arg -cq9"
359+ db_cmd=$( ssh_wrap " $db_cmd " )
365360
366361 # Actually EXECUTE the commands
367362 pids=()
363+ $verbose && echo
368364 for t in $valid_modules ; do
369365 if [[ " $modules " == * " $t " * ]]; then
370- targ_var=${t} _target; targ=${! targ_var}
371- cmd_var=${t} _cmd; cmd=${! cmd_var}
366+ targ_var=" ${t} _target" ; targ=" ${! targ_var} "
367+ cmd_var=" ${t} _cmd" ; cmd=" ${! cmd_var} "
368+ [[ -n $compression_tool ]] && cmd=" $cmd | $compression_tool -cq9"
372369 echo " $mname $t : $targ "
373- # In verbose mode, output the command, but mask the password and eliminate whitespace by echoing with word splitting.
374- # shellcheck disable=2086
375- $verbose && echo ${cmd// password=\' $source_db_password \' / password=\' $source_db_password_mask \' }
376- if $dry_run ; then
377- echo -e " ${red} Not executed. This is a dry run.$norm \n"
378- else
370+ # In verbose mode, output the command, but mask password. Eliminate whitespace with `xargs`.
371+ # Ref: https://stackoverflow.com/questions/369758/how-to-trim-whitespace-from-a-bash-variable
372+ $verbose && echo " ${bold} Command:$norm ${cmd// password=\' $source_db_password \' / password=\' $source_db_password_mask \' } " | xargs
373+ if ! $dry_run ; then
379374 cmd=" $cmd > '$targ '"
380375 # Execute the command, and handle success/fail scenarios
381376 eval " $cmd " && success=true || success=false
@@ -384,13 +379,14 @@ for mname in $mnames; do
384379 rm " $targ "
385380 echo " Removed $( basename " $targ " ) because the $t backup failed." >&2
386381 fi
387- fi
388- fi &
389- pids+=( $! )
382+ fi &
383+ pids+=( $! )
384+ fi
390385 done
391386
392387 # TODO: It'd be nice if we check if any of the steps failed, and exit non-zero if so.
393388 wait " ${pids[@]} "
389+ $dry_run && echo -e " ${red} Commands not executed. This is a dry run.$norm \n"
394390 echo " $action_word of $mname environment is complete!"
395391
396392 # Unset environment variables
0 commit comments