-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathzfsrestore
executable file
·343 lines (298 loc) · 8.27 KB
/
zfsrestore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#!/sbin/sh
#
# ZRescue - ZFS Backup/Restore
#
# ZFS archives uses for bare-metal restore
# and systems cloning.
#
# Archive names can be incremental, like that:
# [hostname].[pool|pool%dataset].n.zfs<.gz>, n=0,1,2...
# Note: Do not rename archive files! Filenames will
# use to recovery purposes.
#
# Version 2.0 (C) 2009,2016 Y.Voinov
#
# If you not specify pool/dataset name in command line,
# it will be read from archive name.
#
#pragma ident "@(#)zfsrestore.sh 2.0 10/22/09 YV"
#
#############
# Variables #
#############
# Environment variables
PATH=/usr/local/bin:/sbin:/usr/bin:$PATH
# Copyright and version
PKG_NAME="ZRescue"
PKG_VERSION="2.0"
COPY="Copyright (C) 2009, Yuri Voinov"
# Archiver name
ARC_NAME="gzip"
# Archive file suffix
ARC_SUFFIX=".gz"
# GZip default compression level
COMP_LEVEL="9"
# Default archive extension
ext="zfs"
# OS utilities
BASENAME=`which basename`
CAT=`which cat`
CUT=`which cut`
DATE=`which date`
ECHO=`which echo`
EXPR=`which expr`
GETOPT=`which getopt`
FILE=`which file`
GREP=`which grep`
GZIP=`which gzip`
HOSTNAME=`which hostname`
ID=`which id`
PRINTF=`which printf`
SED=`which sed`
SSH=`which ssh`
UNAME=`which uname`
WHOAMI=`which whoami`
ZFS=`which zfs`
OS_VER=`$UNAME -r|$CUT -f2 -d"."`
OS_NAME=`$UNAME -s|$CUT -f1 -d" "`
OS_FULL=`$UNAME -sr`
# System name
system=`$HOSTNAME`
# Snapshots extension by default
# Correct it if these snapshots already exists in the system
SNAP_EXT="$system""_""$PKG_NAME""-""$PKG_VERSION""_snapshot"
###############
# Subroutines #
###############
check_os ()
{
# Check OS
$PRINTF "Checking OS... "
if [ "$OS_NAME" = "SunOS" -a "$OS_VER" -lt "10" ]; then
$ECHO "ERROR: Unsupported OS: $OS_FULL"
$ECHO "Exiting..."
exit 1
else
$ECHO "$OS_FULL"
fi
}
check_root ()
{
# Check if user root
$PRINTF "Checking super-user... "
if [ -f /usr/xpg4/bin/id ]; then
WHO=`/usr/xpg4/bin/id -n -u`
elif [ "`$ID | $CUT -f1 -d" "`" = "uid=0(root)" ]; then
WHO="root"
else
WHO=$WHOAMI
fi
if [ ! "$WHO" = "root" ]; then
$ECHO
$ECHO "ERROR: You must be super-user to run this script."
exit 1
fi
$ECHO "$WHO"
}
check_fs_exists ()
{
# Check filesystem exists
arg_fs=$1
ret=`$ZFS list -H -o name $arg_fs > /dev/null 2>&1; $ECHO $?`
if [ "$ret" != "0" ]; then
$ECHO "ERROR: ZFS pool/dataset $arg_fs does not exist."
$ECHO " Please specify another ZFS."
$ECHO "Exiting..."
exit 1
fi
}
check_hostname ()
{
# Check if backup hostname is the same target hostname
arg_backup_hostname=$1
arg_remote_hostname=$2
if [ "$arg_backup_hostname" != "$arg_remote_hostname" ]; then
$ECHO "WARNING: Backup was taken from $arg_backup_hostname host,"
$ECHO " target host is $arg_remote_hostname."
fi
}
copyright_and_version ()
{
# Print package and copyright info
$ECHO
$ECHO "$PKG_NAME $PKG_VERSION $COPY"
$ECHO
}
usage_note ()
{
copyright_and_version
$ECHO "Usage: `$BASENAME $0` [-v] [-n] [-r] [-f] [/mntpoint/archive|local fs] [pool|dataset] [host]"
$ECHO
$ECHO "Note: Compression will use if GZip installed,"
$ECHO " both for local archives or remote streams transfer."
$ECHO "Beware: GZip must be installed in both nodes in case of remote backup."
exit 1
}
archive_type ()
{
# Check archive type using extension and header check
arg_file=$1
if [ ! -z "`$ECHO $arg_file | $GREP $ARC_SUFFIX$`" -a \
! -z "`$FILE "$arg_file" | $GREP $ARC_NAME`" ]; then
$ECHO "$ARC_NAME"
elif [ ! -z "`$ECHO $arg_file | $GREP $ext$`" -a \
! -z "`$FILE "$arg_file" | $GREP $ext`" ]; then
$ECHO "$ext"
else
$ECHO "unknown"
fi
}
archive_exists ()
{
# Check archive exist and it readable
arg_arc=$1
# First check archive exists and readable
if [ ! -f "$arg_arc" -a ! -r "$arg_arc" ]; then
$ECHO "ERROR: Archive $arg_arc does not exist"
$ECHO " or you haven't permissions to read."
$ECHO "Exiting..."
exit 1
fi
# Second we'll check archive type
if [ "`archive_type $arg_arc`" = "$ARC_NAME" -a \
"`archive_type $arg_arc`" = " $ext" ]; then
$ECHO "ERROR: Archive $arg_arc it has wrong type."
$ECHO "Exiting..."
exit 1
fi
}
destroy_fs ()
{
# Destroy filesystem(s) recursively
arg_fs=$1
$ZFS destroy -r "$arg_fs" > /dev/null 2>&1
# Check exit code
if [ "`$ECHO $?`" != "0" ]; then
$ECHO "WARNING: Filesystem $arg_fs does not exists."
fi
}
create_snap ()
{
# Create snapshot recursively
arg_filesystem=$1
$ZFS snapshot -r "$arg_filesystem@$SNAP_EXT"
}
zfs_receive ()
{
# Receive filesystem(s) from archive
arg_fs=$1 # Archive or fs to receive
arg_filesys=$2 # Target fs
arg_remote_host=$3 # Remote host
# Verbose output flag set in interactive mode
if [ "$verbose" = "1" ]; then
verb="v"
fi
# When use compression, restore fs with archiver
if [ "$remote_mode" != "1" -a "$remote_file_archive" != "1" ]; then
if [ "$compress" = "1" -a "`archive_type $arg_fs`" = "$ARC_NAME" ]; then
# Send from local file with archiver
$GZIP -d -c $arg_fs | $ZFS receive -dF"$verb" "$arg_filesys"
elif [ "`archive_type $arg_file`" = "$ext" ]; then
# Send from local file without archiver
$ZFS receive -dF"$verb" "$arg_filesys" < "$arg_fs"
fi
elif [ "$remote_mode" = "1" -a "$remote_file_archive" != "1" ]; then
if [ "$compress" = "1" ]; then
# Send to remote from local fs with compression
create_snap $arg_fs
$ZFS send -R$verb $arg_fs@$SNAP_EXT|$GZIP -$COMP_LEVEL -|\
$SSH $arg_remote_host "/bin/gzip -c -d -|/sbin/zfs receive -dF$verb $arg_filesys"
else
# Send to remote from local fs without compression
create_snap $arg_fs
$ZFS send -R$verb $arg_fs@$SNAP_EXT|\
$SSH $arg_remote_host "/sbin/zfs receive -dF$verb $arg_filesys"
fi
# Destroy local and remote snapshots
$SSH $arg_remote_host "/sbin/zfs destroy -r `/sbin/zfs list -H -o name -t snapshot|\
/bin/grep $arg_filesys@$SNAP_EXT`> /dev/null 2>&1"
destroy_fs $arg_fs@$SNAP_EXT
elif [ "$remote_mode" = "1" -a "$remote_file_archive" = "1" -a "`archive_type $arg_fs`" = "$ARC_NAME" ]; then
check_hostname `$ECHO "$arg_fs"|$CUT -f1 -d"."` $arg_remote_host
if [ "$compress" = "1" ]; then
# Send to remote from local archive with compression
$CAT $arg_fs|$SSH $arg_remote_host "/bin/gzip -c -d -|/sbin/zfs receive -dF$verb $arg_filesys"
else
# Send to remote from local archive without compression
$CAT $arg_fs|$SSH $arg_remote_host "/sbin/zfs receive -dF$verb $arg_filesys"
fi
# Destroy remote snapshots
$SSH $arg_remote_host "/sbin/zfs destroy -r `/sbin/zfs list -H -o name -t snapshot|\
/bin/grep $arg_filesys@$SNAP_EXT`> /dev/null 2>&1"
fi
}
##############
# Main block #
##############
# Checking OS
check_os
# Checking root
check_root
# Check command-line arguments
if [ "x$*" = "x" ]; then
# If arguments list empty, show usage note
usage_note
else
arg_list=$*
# Parse command line
set -- `$GETOPT fFrRnNvVhH: $arg_list` || {
usage_note
}
# Read arguments
for i in $arg_list
do
case $i in
-f|-F) remote_file_archive="1";;
-r|-R) remote_mode="1";;
-n|-N) compress_off="1";;
-v|-V) verbose="1";;
-h|-H|\?) usage_note;;
*) shift
archive=$1
filesystem=$2;
remote_host=$3
break;;
esac
shift
done
# Remove trailing --
shift `$EXPR $OPTIND - 1`
fi
# Check filesystem exists
check_fs_exists $filesystem
$ECHO "*** BEGIN: ZFS restore for $filesystem at `$DATE`."
# Check archiver
if [ ! -f "$GZIP" -a ! -x "$GZIP" -o "$compress_off" = "1" ]; then
$ECHO "INFO: Compression will NOT be used. GZip not found or compression disabled."
compress="0"
elif [ -f "$GZIP" -a -x "$GZIP" -a "$compress_off" != "1" ]; then
$ECHO "INFO: Data will be compressed with gzip -$COMP_LEVEL."
compress="1"
fi
# Check archive file exists and correct type when non-remote operation
if [ "$remote_mode" != "1" -o "x$remote_file_archive" != "x" ]; then
# If filesystem not specified, let's get it from file name
if [ "x$filesystem" = "x" ]; then
# Set initial archive file name
# Replase slashes with % if dataset specified in non-remote mode
filesystem=`$ECHO "$archive" | $CUT -f2 -d"." | $SED -e 's/%/\//g' | $CUT -f1 -d"/"`
fi
archive_exists $archive
fi
# First destroy all snapshots recursively
destroy_fs "$filesystem@$SNAP_EXT"
# Restore ZFS pool/dataset
zfs_receive $archive $filesystem $remote_host
# Finally destroy all snapshots recursively
destroy_fs "$filesystem@$SNAP_EXT"
$ECHO "*** DONE: ZFS restore for $filesystem at `$DATE`."