1- #! /bin/bash
1+ #! /usr/ bin/env bash
22# Docker Volume File Backup and Restore Tool
33# Easily tar up a volume on a local (or remote) engine
44# Inspired by CLIP from Lukasz Lach
55
66set -Eeo pipefail
77
88handle_error () {
9- exit_code=$?
9+ case $# in
10+ 1) LINE_NUMBER=$1 ; EXIT_CODE=$? ;;
11+ 2) LINE_NUMBER=$1 ; EXIT_CODE=$2 ;;
12+ * ) LINE_NUMBER=$LINENO ; EXIT_CODE=1 ;;
13+ esac
14+
1015 if [ -n " ${VACKUP_FAILURE_SCRIPT} " ]; then
11- /bin/bash " ${VACKUP_FAILURE_SCRIPT} " " $1 " $exit_code
16+ /bin/bash " ${VACKUP_FAILURE_SCRIPT} " " $LINE_NUMBER " " $EXIT_CODE "
1217 fi
13- exit $exit_code
18+
19+ exit " $EXIT_CODE "
1420}
1521
1622trap ' handle_error $LINENO' ERR
1723
1824usage () {
1925cat << EOF
2026
21- "Docker Volume Backup". Replicates image management commands for volumes.
27+ "Docker Volume Backup". Replicates container image management commands for container volumes.
2228
23- export/import copies files between a host tarball and a volume. For making
24- volume backups and restores.
29+ export/import copies files between a host tarball and a volume.
30+ For making volume backups and restores on your local file system .
2531
26- save/load copies files between an image and a volume. For when you want to use
27- image registries as a way to push/pull volume data .
32+ save/load copies files between a container image and a container volume.
33+ For storing container volumes in images and push/pulling to registries .
2834
29- Usage:
35+ Usage:
3036
3137vackup export VOLUME FILE
32- Creates a gzip'ed tarball in current directory from a volume
38+ Creates a gzip'ed tarball in current directory from a container volume
3339
3440vackup import FILE VOLUME
35- Extracts a gzip'ed tarball into a volume
41+ Extracts a gzip'ed tarball into a container volume
3642
3743vackup save VOLUME IMAGE
38- Copies the volume contents to a busybox image in the /volume-data directory
44+ Copies a container volume to a busybox container image in the /volume-data directory
3945
4046vackup load IMAGE VOLUME
41- Copies /volume-data contents from an image to a volume
47+ Copies /volume-data from a container image to a container volume
4248
4349EOF
4450}
4551
52+ error () {
53+ if [ " $1 " == ' u' ] || [ " $1 " == ' usage' ]; then
54+ USAGE=1
55+ MESSAGE=$2
56+ CODE=$3
57+ else
58+ USAGE=0
59+ MESSAGE=$1
60+ CODE=$2
61+ fi
62+
63+ if [ -z " $MESSAGE " ]; then
64+ echo 1>&2 ' Error'
65+ else
66+ echo 1>&2 " Error: $MESSAGE "
67+ fi
68+
69+ if [ $USAGE -eq 1 ]; then
70+ usage 1>&2
71+ fi
72+
73+ if [ -z " $CODE " ]; then
74+ CODE=1
75+ fi
76+
77+ LINE_NUMBER=$( caller | awk ' { print $1 }' )
78+ handle_error " $LINE_NUMBER " " $CODE "
79+ }
80+
81+ fulldirname () {
82+ DIRECTORY=$( dirname " $1 " )
83+
84+ case " $DIRECTORY " in
85+ /* ) ;;
86+ * ) DIRECTORY=" $( pwd) /$DIRECTORY " ;;
87+ esac
88+
89+ # Use realpath if available, else fallback to cd/pwd for macOS compatibility
90+ if command -v realpath > /dev/null 2>&1 ; then
91+ DIRECTORY=$( realpath " $DIRECTORY " )
92+ else
93+ DIRECTORY=$( cd " $DIRECTORY " && pwd)
94+ fi
95+
96+ echo " $DIRECTORY "
97+ }
98+
4699if [ -z " $1 " ] || [ " $1 " == " -h" ] || [ " $1 " == " --help" ]; then
47100 usage
48101 exit 0
51104cmd_export () {
52105 VOLUME_NAME=" $2 "
53106 FILE_NAME=" $3 "
54-
107+
55108 if [ -z " $VOLUME_NAME " ] || [ -z " $FILE_NAME " ]; then
56- echo " Error: Not enough arguments"
57- usage
58- exit 1
109+ error usage ' Not enough arguments'
59110 fi
60-
111+
61112 if ! docker volume inspect --format ' {{.Name}}' " $VOLUME_NAME " ;
62113 then
63- echo " Error: Volume $VOLUME_NAME does not exist"
64- exit 1
114+ error " Volume $VOLUME_NAME does not exist"
65115 fi
66116
67- # TODO: check if file exists on host, if it does
68- # create a option for overwrite and check if that's set
69- # TODO: if FILE_NAME starts with / we need to error out
70- # unless we can translate full file paths
117+ # TODO: check if file exists on host, if it does, create overwrite option and check if set
118+
119+ DIRECTORY= $( fulldirname " $FILE_NAME " )
120+ FILE_NAME= $( basename " $FILE_NAME " )
71121
72122 if ! docker run --rm \
73- -v " $VOLUME_NAME " :/vackup- volume \
74- -v " $( pwd ) " :/vackup \
123+ -v " $VOLUME_NAME " :/volume-data \
124+ -v " $DIRECTORY " :/mount-volume \
75125 busybox \
76- tar -zcvf /vackup /" $FILE_NAME " -C /vackup- volume . ;
126+ tar -cvzf /mount-volume /" $FILE_NAME " -C /volume-data . ;
77127 then
78- echo " Error: Failed to start busybox backup container"
79- exit 1
128+ error ' Failed to start busybox backup container'
80129 fi
81130
82131 echo " Successfully tar'ed volume $VOLUME_NAME into file $FILE_NAME "
@@ -85,32 +134,37 @@ cmd_export() {
85134cmd_import () {
86135 FILE_NAME=" $2 "
87136 VOLUME_NAME=" $3 "
88-
137+
89138 if [ -z " $VOLUME_NAME " ] || [ -z " $FILE_NAME " ]; then
90- echo " Error: Not enough arguments"
91- usage
92- exit 1
139+ error usage ' Not enough arguments'
93140 fi
94-
141+
95142 if ! docker volume inspect --format ' {{.Name}}' " $VOLUME_NAME " ;
96143 then
97- echo " Error : Volume $VOLUME_NAME does not exist"
144+ echo " Warning : Volume $VOLUME_NAME does not exist, creating... "
98145 docker volume create " $VOLUME_NAME "
99146 fi
100147
101- # TODO: check if file exists on host, if it does
102- # create a option for overwrite and check if that's set
103- # TODO: if FILE_NAME starts with / we need to error out
104- # unless we can translate full file paths
148+ if [ ! -r " $FILE_NAME " ]; then
149+ echo " Error: Could not find or open tar file $FILE_NAME "
150+ exit 1
151+ fi
152+
153+ if [ -d " $FILE_NAME " ]; then
154+ echo " Error: $FILE_NAME is a directory"
155+ exit 1
156+ fi
157+
158+ DIRECTORY=$( fulldirname " $FILE_NAME " )
159+ FILE_NAME=$( basename " $FILE_NAME " )
105160
106161 if ! docker run --rm \
107- -v " $VOLUME_NAME " :/vackup- volume \
108- -v " $( pwd ) " :/vackup \
162+ -v " $VOLUME_NAME " :/volume-data \
163+ -v " $DIRECTORY " :/mount-volume \
109164 busybox \
110- tar -xvzf /vackup /" $FILE_NAME " -C /vackup- volume;
165+ tar -xvzf /mount-volume /" $FILE_NAME " -C /volume-data ;
111166 then
112- echo " Error: Failed to start busybox container"
113- exit 1
167+ error ' Failed to start busybox container'
114168 fi
115169
116170 echo " Successfully unpacked $FILE_NAME into volume $VOLUME_NAME "
@@ -121,58 +175,55 @@ cmd_save() {
121175 IMAGE_NAME=" $3 "
122176
123177 if [ -z " $VOLUME_NAME " ] || [ -z " $IMAGE_NAME " ]; then
124- echo " Error: Not enough arguments"
125- usage
126- exit 1
178+ error usage ' Not enough arguments'
127179 fi
128180
129- if ! docker volume inspect --format ' {{.Name}}' " $VOLUME_NAME " ;
181+ if ! docker volume inspect --format ' {{.Name}}' " $VOLUME_NAME " ;
130182 then
131- echo " Error: Volume $VOLUME_NAME does not exist"
132- exit 1
183+ error " Volume $VOLUME_NAME does not exist"
133184 fi
134185
135186 if ! docker run \
187+ --pull missing \
136188 -v " $VOLUME_NAME " :/mount-volume \
137189 busybox \
138190 cp -Rp /mount-volume/. /volume-data/;
139191 then
140- echo " Error: Failed to start busybox container"
141- exit 1
192+ error ' Failed to start busybox container'
142193 fi
143194
195+ # FIXME: this command assumes that no containers started between the cp and container rm commands.
196+ # It would be safer to capture ID of the busybox container on start, then delete that ID
144197 CONTAINER_ID=$( docker ps -lq)
145198
146- docker commit -m " saving volume $VOLUME_NAME to /volume-data" " $CONTAINER_ID " " $IMAGE_NAME "
199+ docker commit -c " LABEL com.docker.desktop.volume-contents.action=true " - m " saving volume $VOLUME_NAME to /volume-data" " $CONTAINER_ID " " $IMAGE_NAME "
147200
148201 docker container rm " $CONTAINER_ID "
149-
202+
150203 echo " Successfully copied volume $VOLUME_NAME into image $IMAGE_NAME , under /volume-data"
151204}
152205
153206cmd_load () {
154207 IMAGE_NAME=" $2 "
155208 VOLUME_NAME=" $3 "
156-
209+
157210 if [ -z " $VOLUME_NAME " ] || [ -z " $IMAGE_NAME " ]; then
158- echo " Error: Not enough arguments"
159- usage
160- exit 1
211+ error usage ' Not enough arguments'
161212 fi
162213
163- if ! docker volume inspect --format ' {{.Name}}' " $VOLUME_NAME " ;
214+ if ! docker volume inspect --format ' {{.Name}}' " $VOLUME_NAME " ;
164215 then
165- echo " Volume $VOLUME_NAME does not exist, creating..."
216+ echo " Warning: Volume $VOLUME_NAME does not exist, creating..."
166217 docker volume create " $VOLUME_NAME "
167218 fi
168-
219+
220+ # FIXME: this command assumes the image we're copying from has the cp command available
169221 if ! docker run --rm \
170222 -v " $VOLUME_NAME " :/mount-volume \
171223 " $IMAGE_NAME " \
172- cp -Rp /volume-data/. /mount-volume/;
224+ cp -Rp /volume-data/. /mount-volume/;
173225 then
174- echo " Error: Failed to start container from $IMAGE_NAME "
175- exit 1
226+ error " Failed to start container from $IMAGE_NAME "
176227 fi
177228
178229 echo " Successfully copied /volume-data from $IMAGE_NAME into volume $VOLUME_NAME "
@@ -184,6 +235,7 @@ case "$COMMAND" in
184235 import) cmd_import " $@ " ;;
185236 save) cmd_save " $@ " ;;
186237 load) cmd_load " $@ " ;;
238+ * ) echo " Error: '$COMMAND ' is not a recognized command" ; usage ;;
187239esac
188240
189241exit 0
0 commit comments