source: src-sh/lpreserver/backend/functions.sh @ 59f367e

9.2-releasereleng/10.0releng/10.0.1releng/10.0.2
Last change on this file since 59f367e was 59f367e, checked in by Kris Moore <kris@…>, 11 months ago

Add bunch of new functionality to lpreserver

Replace "mirror" with "zpool" sub-cmd, which will allow you to
attach / detach / online / offline / list various zpool drives

  • Important notes *

When attaching a new disk to a zpool, the drive will
be wiped and re-formatted with a 'freebsd-zfs' file-system of the same
size as the first-disk in th zpool array.

If the first disk has a swap partition it will be created also,
but not activated, since advanced users can handle that as the see fit.

Last but not least, GRUB will be stamped on the new disk, making it
bootable should another drive in the array go bad.

  • Property mode set to 100755
File size: 15.0 KB
Line 
1#!/bin/sh
2# Functions / variables for lpreserver
3######################################################################
4# DO NOT EDIT
5
6# Source external functions
7. /usr/local/share/pcbsd/scripts/functions.sh
8
9# Installation directory
10PROGDIR="/usr/local/share/lpreserver"
11
12# Location of settings
13DBDIR="/var/db/lpreserver"
14if [ ! -d "$DBDIR" ] ; then mkdir -p ${DBDIR} ; fi
15
16CMDLOG="${DBDIR}/lp-lastcmdout"
17CMDLOG2="${DBDIR}/lp-lastcmdout2"
18REPCONF="${DBDIR}/replication"
19LOGDIR="/var/log/lpreserver"
20REPLOGSEND="${LOGDIR}/lastrep-send-log"
21REPLOGRECV="${LOGDIR}/lastrep-recv-log"
22MSGQUEUE="${DBDIR}/.lpreserver.msg.$$"
23export DBDIR LOGDIR PROGDIR CMDLOG REPCONF REPLOGSEND REPLOGRECV MSGQUEUE
24
25# Create the logdir
26if [ ! -d "$LOGDIR" ] ; then mkdir -p ${LOGDIR} ; fi
27
28#Set our Options
29setOpts() {
30  if [ -e "${DBDIR}/recursive-off" ] ; then
31    export RECURMODE="OFF"
32  else
33    export RECURMODE="ON"
34  fi
35
36  if [ -e "${DBDIR}/emaillevel" ] ; then
37    export EMAILMODE="`cat ${DBDIR}/emaillevel`"
38  fi
39
40  if [ -e "${DBDIR}/duwarn" ] ; then
41    export DUWARN="`cat ${DBDIR}/duwarn`"
42  else
43    export DUWARN=85
44  fi
45
46  case $EMAILMODE in
47      ALL|WARN|ERROR) ;;
48        *) export EMAILMODE="WARN";;
49  esac
50
51  if [ -e "${DBDIR}/emails" ] ; then
52    export EMAILADDY="`cat ${DBDIR}/emails`"
53  fi
54
55}
56setOpts
57
58
59# Check if a directory is mounted
60isDirMounted() {
61  mount | grep -q "on $1 ("
62  return $?
63}
64
65mkZFSSnap() {
66  if [ "$RECURMODE" = "ON" ] ; then
67     flags="-r"
68  else
69     flags="-r"
70  fi
71  zdate=`date +%Y-%m-%d-%H-%M-%S`
72  zfs snapshot $flags ${1}@$2${zdate} >${CMDLOG} 2>${CMDLOG}
73  return $?
74}
75
76listZFSSnap() {
77  zfs list -t snapshot | grep -e "^NAME" -e "^${1}@"
78}
79
80rmZFSSnap() {
81  `zfs list -t snapshot | grep -q "^$1@$2 "` || exit_err "No such snapshot!"
82  if [ "$RECURMODE" = "ON" ] ; then
83     flags="-r"
84  else
85     flags="-r"
86  fi
87  zfs destroy -r ${1}@${2} >${CMDLOG} 2>${CMDLOG}
88  return $?
89}
90
91revertZFSSnap() {
92  # Make sure this is a valid snapshot
93  `zfs list -t snapshot | grep -q "^$1@$2 "` || exit_err "No such snapshot!"
94
95  # Rollback the snapshot
96  zfs rollback -R -f ${1}@$2
97}
98
99enable_cron()
100{
101   cronscript="${PROGDIR}/backend/runsnap.sh"
102
103   # Make sure we remove any old entries for this dataset
104   cat /etc/crontab | grep -v " $cronscript $1" > /etc/crontab.new
105   mv /etc/crontab.new /etc/crontab
106   if [ "$2" = "OFF" ] ; then
107      return
108   fi
109
110   case $2 in
111       daily) cLine="0       $4      *       *       *" ;;
112      hourly) cLine="0       *       *       *       *" ;;
113       30min) cLine="0,30    *       *       *       *" ;;
114       10min) cLine="*/10    *       *       *       *" ;;
115        5min) cLine="*/5     *       *       *       *" ;;
116          *) exit_err "Invalid time specified" ;;
117   esac
118
119   echo -e "$cLine\troot    ${cronscript} $1 $3" >> /etc/crontab
120}
121
122enable_watcher()
123{
124   cronscript="${PROGDIR}/backend/zfsmon.sh"
125
126   # Check if the zfs monitor is already enabled
127   grep -q " $cronscript" /etc/crontab
128   if [ $? -eq 0 ] ; then return; fi
129
130   cLine="*/30    *       *       *       *"
131
132   echo -e "$cLine\troot    ${cronscript}" >> /etc/crontab
133}
134
135snaplist() {
136  zfs list -t snapshot | grep "^${1}@" | cut -d '@' -f 2 | awk '{print $1}'
137}
138
139echo_log() {
140   echo "`date`: $@" >> ${LOGDIR}/lpreserver.log
141}
142
143# E-Mail a message to the set addresses
144# 1 = subject tag
145# 2 = Message
146email_msg() {
147   if [ -z "$EMAILADDY" ] ; then return ; fi
148   echo -e "$2"  | mail -s "$1 - `hostname`" $EMAILADDY
149}
150
151queue_msg() {
152  echo -e "$1" >> ${MSGQUEUE}
153  if [ -n "$2" ] ; then
154    cat $2 >> ${MSGQUEUE}
155  fi
156}
157
158echo_queue_msg() {
159  if [ ! -e "$MSGQUEUE" ] ; then return ; fi
160  cat ${MSGQUEUE}
161  rm ${MSGQUEUE}
162}
163
164add_rep_task() {
165  # add freenas.8343 backupuser 22 tank1/usr/home/kris tankbackup/backups sync
166  HOST=$1
167  USER=$2
168  PORT=$3
169  LDATA=$4
170  RDATA=$5
171  TIME=$6
172
173  case $TIME in
174     [0-9][0-9]|sync)  ;;
175     *) exit_err "Invalid time: $TIME"
176  esac
177 
178  echo "Adding replication task for local dataset $LDATA"
179  echo "----------------------------------------------------------"
180  echo "   Remote Host: $HOST" 
181  echo "   Remote User: $USER" 
182  echo "   Remote Port: $PORT" 
183  echo "Remote Dataset: $RDATA" 
184  echo "          Time: $TIME" 
185  echo "----------------------------------------------------------"
186  echo "Don't forget to ensure that this user / dataset exists on the remote host"
187  echo "with the correct permissions!"
188
189  rem_rep_task "$LDATA"
190  echo "$LDATA:$TIME:$HOST:$USER:$PORT:$RDATA" >> ${REPCONF}
191
192  if [ "$TIME" != "sync" ] ; then
193    cronscript="${PROGDIR}/backend/runrep.sh"
194    cLine="0    $TIME       *       *       *"
195    echo -e "$cLine\troot    ${cronscript} ${LDATA}" >> /etc/crontab
196  fi
197}
198
199rem_rep_task() {
200  if [ ! -e "$REPCONF" ] ; then return ; fi
201  cat ${REPCONF} | grep -v "^${1}:" > ${REPCONF}.tmp
202  mv ${REPCONF}.tmp ${REPCONF}
203
204  # Make sure we remove any old replication entries for this dataset
205  cronscript="${PROGDIR}/backend/runrep.sh"
206  cat /etc/crontab | grep -v " $cronscript $1" > /etc/crontab.new
207  mv /etc/crontab.new /etc/crontab
208}
209
210list_rep_task() {
211  if [ ! -e "$REPCONF" ] ; then return ; fi
212
213  echo "Scheduled replications:"
214  echo "---------------------------------"
215
216  while read line
217  do
218     LDATA=`echo $line | cut -d ':' -f 1`
219     TIME=`echo $line | cut -d ':' -f 2`
220     HOST=`echo $line | cut -d ':' -f 3`
221     USER=`echo $line | cut -d ':' -f 4`
222     PORT=`echo $line | cut -d ':' -f 5`
223     RDATA=`echo $line | cut -d ':' -f 6`
224
225     echo "$LDATA -> $USER@$HOST[$PORT]:$RDATA Time: $TIME"
226
227  done < ${REPCONF}
228}
229
230check_rep_task() {
231  export DIDREP=0
232  if [ ! -e "$REPCONF" ] ; then return 0; fi
233
234  repLine=`cat ${REPCONF} | grep "^${1}:"`
235  if [ -z "$repLine" ] ; then return 0; fi
236
237  # We have a replication task for this dataset, lets check if we need to do it now
238  LDATA="$1"
239  REPTIME=`echo $repLine | cut -d ':' -f 2`
240
241  # Export the replication variables we will be using
242  export REPHOST=`echo $repLine | cut -d ':' -f 3`
243  export REPUSER=`echo $repLine | cut -d ':' -f 4`
244  export REPPORT=`echo $repLine | cut -d ':' -f 5`
245  export REPRDATA=`echo $repLine | cut -d ':' -f 6`
246
247  if [ "$2" = "force" ] ; then
248     # Ready to do a forced replication
249     export DIDREP=1
250     echo_log "Starting replication MANUAL task on ${DATASET}: ${REPLOGSEND}"
251     queue_msg "`date`: Starting replication MANUAL task on ${DATASET}\n"
252     start_rep_task "$LDATA"
253     return $?
254  fi
255
256  # If we are checking for a sync task, and the rep isn't marked as sync we can return
257  if [ "$2" = "sync" -a "$REPTIME" != "sync" ] ; then return 0; fi
258
259  # Is this a sync-task we do at the time of a snapshot?
260  if [ "$2" = "sync" -a "$REPTIME" = "sync" ] ; then
261     export DIDREP=1
262     echo_log "Starting replication SYNC task on ${DATASET}: ${REPLOGSEND}"
263     queue_msg "`date`: Starting replication SYNC task on ${DATASET}\n"
264     start_rep_task "$LDATA"
265     return $?
266  else
267     # Ready to do a scheduled replication
268     export DIDREP=1
269     echo_log "Starting replication SCHEDULED task on ${DATASET}: ${REPLOGSEND}"
270     queue_msg "`date`: Starting replication SCHEDULED task on ${DATASET}\n"
271     start_rep_task "$LDATA"
272     return $?
273  fi
274}
275
276start_rep_task() {
277  LDATA="$1"
278  hName=`hostname`
279
280  # Check for the last snapshot marked as replicated already
281  lastSEND=`zfs get -r backup:lpreserver ${LDATA} | grep LATEST | awk '{$1=$1}1' OFS=" " | tail -1 | cut -d '@' -f 2 | cut -d ' ' -f 1`
282
283  # Lets get the last snapshot for this dataset
284  lastSNAP=`zfs list -t snapshot -d 1 -H ${LDATA} | tail -1 | awk '{$1=$1}1' OFS=" " | cut -d '@' -f 2 | cut -d ' ' -f 1`
285 
286  if [ "$lastSEND" = "$lastSNAP" ] ; then
287     queue_msg "`date`: Last snapshot $lastSNAP is already marked as replicated!"
288     return 1
289  fi
290
291  # Starting replication, first lets check if we can do an incremental send
292  if [ -n "$lastSEND" ] ; then
293     zFLAGS="-Rv -I $lastSEND $LDATA@$lastSNAP"
294  else
295     zFLAGS="-Rv $LDATA@$lastSNAP"
296
297     # This is a first-time replication, lets create the new target dataset
298     ssh -p ${REPPORT} ${REPUSER}@${REPHOST} zfs create ${REPRDATA}/${hName} >${CMDLOG} 2>${CMDLOG}
299  fi
300
301  zSEND="zfs send $zFLAGS"
302  zRCV="ssh -p ${REPPORT} ${REPUSER}@${REPHOST} zfs receive -dvuF ${REPRDATA}/${hName}"
303
304  queue_msg "Using ZFS send command:\n$zSEND | $zRCV\n\n"
305
306  # Start up our process
307  $zSEND 2>${REPLOGSEND} | $zRCV >${REPLOGRECV} 2>${REPLOGRECV}
308  zStatus=$?
309  queue_msg "ZFS SEND LOG:\n--------------\n" "${REPLOGSEND}"
310  queue_msg "ZFS RCV LOG:\n--------------\n" "${REPLOGRECV}"
311
312  if [ $zStatus -eq 0 ] ; then
313     # SUCCESS!
314     # Lets mark our new latest snapshot and unmark the last one
315     if [ -n "$lastSEND" ] ; then
316       zfs set backup:lpreserver=' ' ${LDATA}@$lastSEND
317     fi
318     zfs set backup:lpreserver=LATEST ${LDATA}@$lastSNAP
319     echo_log "Finished replication task on ${DATASET}"
320     save_rep_props
321     zStatus=$?
322  else
323     # FAILED :-(
324     # Lets save the output for us to look at later
325     FLOG=${LOGDIR}/lpreserver_failed.log
326     echo "Failed with command:\n$zSEND | $zRCV\n" > ${FLOG}
327     echo "\nSending log:\n" >> ${FLOG}
328     cat ${REPLOGSEND} >> ${FLOG}
329     echo "\nRecv log:\n" >> ${FLOG}
330     cat ${REPLOGRECV} >> ${FLOG}
331     echo_log "FAILED replication task on ${DATASET}: LOGFILE: $FLOG"
332  fi
333
334  return $zStatus
335}
336
337save_rep_props() {
338  # If we are not doing a recursive backup / complete dataset we can skip this
339  if [ "$RECURMODE" != "ON" ] ; then return 0; fi
340  if [ "`basename $DATASET`" != "$DATASET" ] ; then return 0; fi
341
342  echo_log "Saving dataset properties for: ${DATASET}"
343  queue_msg "`date`: Saving dataset properties for: ${DATASET}\n"
344
345  # Lets start by building a list of props to keep
346  rProp=".lp-props-`echo ${REPRDATA}/${hName} | sed 's|/|#|g'`"
347
348  zfs get -r all $DATASET | grep ' local$' | awk '{$1=$1}1' OFS=" " | sed 's| local$||g' \
349        | ssh -p ${REPPORT} ${REPUSER}@${REPHOST} "cat > $rProp"
350  if [ $? -eq 0 ] ; then
351    echo_log "Successful save of dataset properties for: ${DATASET}"
352    queue_msg "`date`: Successful save of dataset properties for: ${DATASET}\n"
353    return 0
354  else
355    echo_log "Failed saving dataset properties for: ${DATASET}"
356    queue_msg "`date`: Failed saving dataset properties for: ${DATASET}\n"
357    return 1
358  fi
359}
360
361listStatus() {
362
363  for i in `grep "${PROGDIR}/backend/runsnap.sh" /etc/crontab | awk '{print $8}'`
364  do
365    echo -e "DATASET - SNAPSHOT - REPLICATION"
366    echo "------------------------------------------"
367
368    lastSEND=`zfs get -r backup:lpreserver ${i} | grep LATEST | awk '{$1=$1}1' OFS=" " | tail -1 | cut -d '@' -f 2 | cut -d ' ' -f 1`
369    lastSNAP=`zfs list -t snapshot -d 1 -H ${i} | tail -1 | awk '{$1=$1}1' OFS=" " | cut -d '@' -f 2 | cut -d ' ' -f 1`
370
371    if [ -z "$lastSEND" ] ; then lastSEND="NONE"; fi
372    if [ -z "$lastSNAP" ] ; then lastSNAP="NONE"; fi
373
374    echo "$i - $lastSNAP - $lastSEND"
375  done
376}
377
378add_zpool_disk() {
379   pool="$1"
380   disk="$2"
381   disk="`echo $disk | sed 's|/dev/||g'`"
382
383   if [ -z "$pool" ] ; then
384      exit_err "No pool specified"
385      exit 0
386   fi
387
388   if [ -z "$disk" ] ; then
389      exit_err "No disk specified"
390      exit 0
391   fi
392
393   if [ ! -e "/dev/$disk" ] ; then
394      exit_err "No such device: $disk"
395      exit 0
396   fi
397
398   # Check if pool exists
399   zpool status $pool >/dev/null 2>/dev/null
400   if [ $? -ne 0 ] ; then exit_err "Invalid pool: $pool"; fi
401
402   # Cleanup the target disk
403   echo "Deleting all partitions on: $disk"
404   rc_nohalt "gpart destroy -F $disk" >/dev/null 2>/dev/null
405   rc_nohalt "dd if=/dev/zero of=/dev/${disk} bs=1m count=1" >/dev/null 2>/dev/null
406   rc_nohalt "dd if=/dev/zero of=/dev/${disk} bs=1m oseek=`diskinfo /dev/${disk} | awk '{print int($3 / (1024*1024)) - 4;}'`" >/dev/null 2>/dev/null
407
408   # Grab the first disk in the pool
409   mDisk=`zpool list -H -v | grep -v "^$pool" | awk '{print $1}' | grep -v "^mirror" | grep -v "^raidz" | head -n 1`
410
411   # Is this MBR or GPT?
412   echo $mDisk | grep -q 's[0-4][a-z]$'
413   if [ $? -eq 0 ] ; then
414      # MBR
415      type="MBR"
416      # Strip off the "a-z"
417      rDiskDev=`echo $mDisk | rev | cut -c 2- | rev`
418   else
419      # GPT
420      type="GPT"
421      # Strip off the "p[1-9]"
422      rDiskDev=`echo $mDisk | rev | cut -c 3- | rev`
423   fi
424
425   # Make sure this disk has a layout we can read
426   gpart show $rDiskDev >/dev/null 2>/dev/null
427   if [ $? -ne 0 ] ; then
428      exit_err "failed to get disk device layout $rDiskDev"
429   fi
430
431   # Get the size of "freebsd-zfs & freebsd-swap"
432   sSize=`gpart show ${rDiskDev} | grep freebsd-swap | cut -d "(" -f 2 | cut -d ")" -f 1`
433   zSize=`gpart show ${rDiskDev} | grep freebsd-zfs | cut -d "(" -f 2 | cut -d ")" -f 1`
434   
435   echo "Creating new partitions on $disk"
436   if [ "$type" = "MBR" ] ; then
437      # Create the new MBR layout
438      rc_halt_s "gpart create -s MBR -f active $disk"
439      rc_halt_s "gpart add -a 4k -t freebsd $disk"     
440      rc_halt_s "gpart set -a active -i 1 $disk"
441      rc_halt_s "gpart create -s BSD ${disk}s1"
442      rc_halt_s "gpart add -t freebsd-zfs -s $zSize ${disk}s1"
443      if [ -n "$sSize" ] ; then
444        rc_halt_s "gpart add -t freebsd-swap -s $sSize ${disk}s1"
445      fi
446      aDev="${disk}s1a"
447   else
448      # Creating a GPT disk
449      rc_halt_s "gpart create -s GPT $disk"
450      rc_halt_s "gpart add -b 34 -s 1M -t bios-boot $disk"
451      rc_halt_s "gpart add -t freebsd-zfs -s $zSize ${disk}"
452      if [ -n "$sSize" ] ; then
453        rc_halt_s "gpart add -t freebsd-swap -s $sSize ${disk}"
454      fi
455      aDev="${disk}p2"
456   fi
457
458   # Now we can insert the target disk
459   echo "Attaching to zpool: $aDev"
460   rc_halt_s "zpool attach $pool $mDisk $aDev"
461
462   # Lastly we need to stamp GRUB
463   echo "Stamping GRUB on: $disk"
464   rc_halt_s "grub-install --force /dev/${disk}"
465
466   echo "Added $disk ($aDev) to zpool $pool. Resilver will begin automatically."
467   exit 0
468}
469
470list_zpool_disks() {
471   pool="$1"
472
473   if [ -z "$pool" ] ; then
474      exit_err "No pool specified"
475      exit 0
476   fi
477
478   # Check if pool exists
479   zpool status $pool >/dev/null 2>/dev/null
480   if [ $? -ne 0 ] ; then exit_err "Invalid pool: $pool"; fi
481
482   zpool list -H -v $pool
483}
484
485rem_zpool_disk() {
486   pool="$1"
487   disk="$2"
488
489   if [ -z "$pool" ] ; then
490      exit_err "No pool specified"
491      exit 0
492   fi
493
494   if [ -z "$disk" ] ; then
495      exit_err "No disk specified"
496      exit 0
497   fi
498
499   # Check if pool exists
500   zpool status $pool >/dev/null 2>/dev/null
501   if [ $? -ne 0 ] ; then exit_err "Invalid pool: $pool"; fi
502
503   zpool detach $pool $disk
504   if [ $? -ne 0 ] ; then
505      exit_err "Failed detaching $disk"
506   fi
507   echo "$disk was detached successfully!"
508   exit 0
509}
510
511offline_zpool_disk() {
512   pool="$1"
513   disk="$2"
514
515   if [ -z "$pool" ] ; then
516      exit_err "No pool specified"
517      exit 0
518   fi
519
520   if [ -z "$disk" ] ; then
521      exit_err "No disk specified"
522      exit 0
523   fi
524
525   # Check if pool exists
526   zpool status $pool >/dev/null 2>/dev/null
527   if [ $? -ne 0 ] ; then exit_err "Invalid pool: $pool"; fi
528
529   zpool offline $pool $disk
530   exit $?
531}
532
533online_zpool_disk() {
534   pool="$1"
535   disk="$2"
536
537   if [ -z "$pool" ] ; then
538      exit_err "No pool specified"
539      exit 0
540   fi
541
542   if [ -z "$disk" ] ; then
543      exit_err "No disk specified"
544      exit 0
545   fi
546
547   # Check if pool exists
548   zpool status $pool >/dev/null 2>/dev/null
549   if [ $? -ne 0 ] ; then exit_err "Invalid pool: $pool"; fi
550
551   zpool online $pool $disk
552   exit $?
553}
Note: See TracBrowser for help on using the repository browser.