Compare commits
2 Commits
8d823a95d3
...
d2ff25bc15
Author | SHA1 | Date |
---|---|---|
Tamás Gérczei | d2ff25bc15 | |
Tamás Gérczei | 8ce8ceff3f |
|
@ -1,14 +1,17 @@
|
||||||
# required format:
|
# required format:
|
||||||
# SOURCE TARGET KEEPDAYS ENABLED
|
# SOURCE TARGET KEEPDAYS MODE ENABLED
|
||||||
# dataset [user@host:]dataset number Y or N
|
# dataset [user@host:]dataset number backup or sync Y or N
|
||||||
|
|
||||||
# we are securing this elsewhere; if we are using this method, we'll need to configure the target host
|
# we are securing this elsewhere; if we are using this method, we'll need to configure the target host
|
||||||
# 'user' needs an authorized and restricted public key for SSH access and sudoer rights on 'remotehost' to execute the following as root:
|
# 'user' needs an authorized and restricted public key for SSH access and sudoer rights on 'remotehost' to execute the following as root:
|
||||||
# /usr/sbin/zfs recv -Feuv datapool/backup, /usr/sbin/zfs destroy -r datapool/backup/*
|
# /usr/sbin/zfs recv -Feuv datapool/backup, /usr/sbin/zfs destroy -r datapool/backup/*
|
||||||
data/home user@remotehost:datapool/backup 7 Y
|
data/home user@remotehost:datapool/backup 7 backup Y
|
||||||
|
|
||||||
# this goes to another pool on this host so no further configuration is necessary
|
# this goes to another pool on this host so no further configuration is necessary
|
||||||
data/config backup/data 5 Y
|
data/config backup/data 5 backup Y
|
||||||
|
|
||||||
# we no longer back this up
|
# we no longer back this up
|
||||||
data/volatile backup/data 2 N
|
data/volatile backup/data 2 backup N
|
||||||
|
|
||||||
|
# we only sync this one
|
||||||
|
data/replica remote/dump 31 sync Y
|
||||||
|
|
158
zfs-backup.sh
158
zfs-backup.sh
|
@ -22,18 +22,18 @@ function snapuse() {
|
||||||
function human() {
|
function human() {
|
||||||
# found at http://unix.stackexchange.com/a/191787
|
# found at http://unix.stackexchange.com/a/191787
|
||||||
nawk 'function human(x) {
|
nawk 'function human(x) {
|
||||||
x[1]/=1024;
|
x[1]/=1024;
|
||||||
if (x[1]>=1000) { x[2]++; human(x); }
|
if (x[1]>=1000) { x[2]++; human(x); }
|
||||||
}
|
}
|
||||||
{a[1]=$1; a[2]=0; human(a); printf "%.2f%s\n",a[1],substr("KMGTEPYZ",a[2]+1,1)}' <<< $1
|
{a[1]=$1; a[2]=0; human(a); printf "%.2f%s\n",a[1],substr("KMGTEPYZ",a[2]+1,1)}' <<< $1
|
||||||
}
|
}
|
||||||
|
|
||||||
function backup() {
|
function backup() {
|
||||||
# check source
|
# check source
|
||||||
check_dataset ${DATASET} || {
|
check_dataset ${DATASET} || {
|
||||||
logger -t $(basename ${0%.sh}) -p user.notice "source dataset \"${DATASET}\" in configuration entry #${COUNTER} does not exist; omitting"
|
logger -t $(basename ${0%.sh}) -p user.notice "source dataset \"${DATASET}\" in configuration entry #${COUNTER} does not exist; omitting"
|
||||||
continue 2
|
continue 2
|
||||||
}
|
}
|
||||||
|
|
||||||
# determine which local snapshots exist already
|
# determine which local snapshots exist already
|
||||||
SNAPSHOTS=( $(zfs list -rt snapshot -d1 -Ho name -S creation ${DATASET} 2>/dev/null) )
|
SNAPSHOTS=( $(zfs list -rt snapshot -d1 -Ho name -S creation ${DATASET} 2>/dev/null) )
|
||||||
|
@ -69,21 +69,24 @@ function backup() {
|
||||||
|
|
||||||
# check target
|
# check target
|
||||||
check_dataset ${SAVETO} || {
|
check_dataset ${SAVETO} || {
|
||||||
logger -t $(basename ${0%.sh}) -p user.notice "target dataset \"${SAVETO}\" in configuration entry #${COUNTER} does not exist; omitting"
|
logger -t $(basename ${0%.sh}) -p user.notice "target dataset \"${SAVETO}\" in configuration entry #${COUNTER} does not exist; omitting"
|
||||||
continue 2
|
continue 2
|
||||||
}
|
}
|
||||||
|
|
||||||
R_SNAPSHOTS=( $(${R_RMOD} zfs list -rt snapshot -d1 -Ho name -S creation ${SAVETO}/$( basename ${DATASET}) 2>/dev/null) )
|
R_SNAPSHOTS=( $(${R_RMOD} zfs list -rt snapshot -d1 -Ho name -S creation ${SAVETO}/$( basename ${DATASET}) 2>/dev/null) )
|
||||||
check_dataset ${SAVETO}/$( basename ${DATASET}) && R_USED_BEFORE=$(snapuse ${SAVETO}/$( basename ${DATASET}))
|
check_dataset ${SAVETO}/$( basename ${DATASET}) && R_USED_BEFORE=$(snapuse ${SAVETO}/$( basename ${DATASET}))
|
||||||
|
|
||||||
# determine current timestamp
|
if [ $MODE == "backup" ]
|
||||||
DATE=$(date +%Y-%m-%d-%H%M)
|
then
|
||||||
|
# determine current timestamp
|
||||||
|
DATE=$(date +%Y-%m-%d-%H%M)
|
||||||
|
|
||||||
# determine the name of the current snapshot to create
|
# determine the name of the current snapshot to create
|
||||||
NEWSNAP="${DATASET}@${DATE}"
|
NEWSNAP="${DATASET}@${DATE}"
|
||||||
|
|
||||||
# take a snapshot
|
# take a snapshot
|
||||||
zfs snapshot -r ${NEWSNAP}
|
zfs snapshot -r ${NEWSNAP}
|
||||||
|
fi
|
||||||
|
|
||||||
# check if source is encrypted
|
# check if source is encrypted
|
||||||
if [ $ENCRYPTION_FEATURE != "disabled" ]
|
if [ $ENCRYPTION_FEATURE != "disabled" ]
|
||||||
|
@ -103,40 +106,50 @@ function backup() {
|
||||||
# local snapshot(s) found
|
# local snapshot(s) found
|
||||||
SNAPMODIFIER="i ${LASTSNAP}"
|
SNAPMODIFIER="i ${LASTSNAP}"
|
||||||
check_dataset ${SAVETO}/$(basename ${LASTSNAP}) || {
|
check_dataset ${SAVETO}/$(basename ${LASTSNAP}) || {
|
||||||
# last local snapshot is not available at the destination location
|
# last local snapshot is not available at the destination location
|
||||||
if [ ${#R_SNAPSHOTS[*]} -ge 1 ]
|
if [ ${#R_SNAPSHOTS[*]} -ge 1 ]
|
||||||
then
|
then
|
||||||
# remote snapshot(s) found
|
# remote snapshot(s) found
|
||||||
R_SNAPMODIFIER="I $(dirname ${DATASET})/$(basename ${R_SNAPSHOTS[*]:(-1)})"
|
R_SNAPMODIFIER="I $(dirname ${DATASET})/$(basename ${R_SNAPSHOTS[0]})"
|
||||||
fi
|
fi
|
||||||
# send any previous snapshots
|
# send any previous snapshots
|
||||||
zfs send -Rcv${RAW_MOD}${R_SNAPMODIFIER} ${LASTSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE}
|
zfs send -Rcv${RAW_MOD}${R_SNAPMODIFIER} ${LASTSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE}
|
||||||
}
|
if [[ ${PIPESTATUS[*]} =~ [1-9]+ ]]
|
||||||
|
then
|
||||||
|
# replication failure
|
||||||
|
logger -t $(basename ${0%.sh}) -p user.notice "failed to replicate ${LASTSNAP} to ${TARGET:-local}:${SAVETO}"
|
||||||
|
else
|
||||||
|
let CHANGES++
|
||||||
|
fi
|
||||||
|
}
|
||||||
else
|
else
|
||||||
# ensure this does not remain in effect
|
# ensure this does not remain in effect
|
||||||
unset SNAPMODIFIER R_SNAPMODIFIER
|
unset SNAPMODIFIER R_SNAPMODIFIER
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# send backup
|
if [ $MODE == "backup" ]
|
||||||
zfs send -Rcv${RAW_MOD}${SNAPMODIFIER} ${NEWSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE}
|
|
||||||
|
|
||||||
# if replication is unsuccessful, omit the aging check so as to prevent data loss
|
|
||||||
if [ $? -eq 0 ]
|
|
||||||
then
|
then
|
||||||
THRESHOLD=$(( $KEEP * 24 * 3600 ))
|
# send backup
|
||||||
for SNAPSHOT in ${SNAPSHOTS[*]}
|
zfs send -Rcv${RAW_MOD}${SNAPMODIFIER} ${NEWSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE}
|
||||||
do
|
|
||||||
TIMESTAMP=$(zfs get -pHo value creation "${SNAPSHOT}")
|
# if replication is unsuccessful, omit the aging check so as to prevent data loss
|
||||||
AGE=$(( $NOW - $TIMESTAMP ))
|
if [[ ! ${PIPESTATUS[*]} =~ [1-9]+ ]]
|
||||||
if [ $AGE -ge $THRESHOLD ]
|
then
|
||||||
then
|
let CHANGES++
|
||||||
zfs destroy -r ${SNAPSHOT} 2>&1 >> ${LOGFILE}
|
THRESHOLD=$(( $KEEP * 24 * 3600 ))
|
||||||
${RMOD:-$R_RMOD} zfs destroy -r ${SAVETO}/$(basename ${SNAPSHOT}) 2>&1 >> ${LOGFILE}
|
for SNAPSHOT in ${SNAPSHOTS[*]}
|
||||||
fi
|
do
|
||||||
done
|
TIMESTAMP=$(zfs get -pHo value creation "${SNAPSHOT}")
|
||||||
else
|
AGE=$(( $NOW - $TIMESTAMP ))
|
||||||
MY_EXIT_CODE=$?
|
if [ $AGE -ge $THRESHOLD ]
|
||||||
logger -t $(basename ${0%.sh}) -p user.notice "failed to replicate ${NEWSNAP} (rc: $MY_EXIT_CODE) to ${TARGET:-local}:${SAVETO}, no aging"
|
then
|
||||||
|
zfs destroy -r ${SNAPSHOT} 2>&1 >> ${LOGFILE}
|
||||||
|
${RMOD:-$R_RMOD} zfs destroy -r ${SAVETO}/$(basename ${SNAPSHOT}) 2>&1 >> ${LOGFILE}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
logger -t $(basename ${0%.sh}) -p user.notice "failed to replicate ${NEWSNAP} to ${TARGET:-local}:${SAVETO}, no aging"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# re-evaluate snapshot data usage
|
# re-evaluate snapshot data usage
|
||||||
|
@ -148,34 +161,38 @@ function backup() {
|
||||||
R_RMOD="ssh ${USER}@${TARGET}"
|
R_RMOD="ssh ${USER}@${TARGET}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
R_USED_AFTER=$(snapuse ${SAVETO}/$( basename ${DATASET}))
|
if [ ${CHANGES:-0} -gt 0 ]
|
||||||
L_DELTA=$(( $L_USED_AFTER - $L_USED_BEFORE ))
|
then
|
||||||
R_DELTA=$(( $R_USED_AFTER - ${R_USED_BEFORE:-0} ))
|
# calculate data volume change
|
||||||
|
R_USED_AFTER=$(snapuse ${SAVETO}/$( basename ${DATASET}))
|
||||||
|
L_DELTA=$(( $L_USED_AFTER - $L_USED_BEFORE ))
|
||||||
|
R_DELTA=$(( $R_USED_AFTER - ${R_USED_BEFORE:-0} ))
|
||||||
|
|
||||||
for DELTA in L_DELTA R_DELTA
|
for DELTA in L_DELTA R_DELTA
|
||||||
do
|
do
|
||||||
unset WHAT WHERE
|
unset WHAT WHERE
|
||||||
eval _DELTA=\$$DELTA
|
eval _DELTA=\$$DELTA
|
||||||
|
|
||||||
if [ $_DELTA -lt 0 ]
|
if [ $_DELTA -lt 0 ]
|
||||||
then
|
then
|
||||||
_DELTA=$(( $_DELTA * -1 ))
|
_DELTA=$(( $_DELTA * -1 ))
|
||||||
WHAT="freed"
|
WHAT="freed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $DELTA =~ ^L ]]
|
if [[ $DELTA =~ ^L ]]
|
||||||
then
|
then
|
||||||
WHERE="$(dirname ${DATASET})"
|
WHERE="$(dirname ${DATASET})"
|
||||||
else
|
else
|
||||||
WHERE="${SAVETO}"
|
WHERE="${SAVETO}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $_DELTA -ne 0 ]
|
if [ $_DELTA -ne 0 ]
|
||||||
then
|
then
|
||||||
_DELTA=$(human $_DELTA)
|
_DELTA=$(human $_DELTA)
|
||||||
logger -t $(basename ${0%.sh}) -p user.notice "$_DELTA ${WHAT:-allocated} in \"${WHERE}\" by backing up \"${DATASET}\""
|
logger -t $(basename ${0%.sh}) -p user.notice "$_DELTA ${WHAT:-allocated} in \"${WHERE}\" by backing up \"${DATASET}\""
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# reset remote configuration
|
# reset remote configuration
|
||||||
unset R_RMOD RMOD RAW_MOD
|
unset R_RMOD RMOD RAW_MOD
|
||||||
|
@ -274,12 +291,13 @@ LOGDIR="/var/log"
|
||||||
# determine session logfile
|
# determine session logfile
|
||||||
LOGFILE="${LOGDIR:-/tmp}/${ME}_${RUNDATE}.txt"
|
LOGFILE="${LOGDIR:-/tmp}/${ME}_${RUNDATE}.txt"
|
||||||
|
|
||||||
while read -u 4 DATASET SAVETO KEEP ENABLED
|
while read -u 4 DATASET SAVETO KEEP MODE ENABLED
|
||||||
# alternate file descriptor in use because SSH might be involved and we can not pass '-n' to it because we need stdin for 'zfs recv'
|
# alternate file descriptor in use because SSH might be involved and we can not pass '-n' to it because we need stdin for 'zfs recv'
|
||||||
do
|
do
|
||||||
let COUNTER++
|
let COUNTER++
|
||||||
if [ ${ENABLED} == "Y" ]
|
if [ ${ENABLED} == "Y" ]
|
||||||
then
|
then
|
||||||
|
# split this function -> snapshot(), sync(), age() ? -> a new backup() would call all three
|
||||||
backup
|
backup
|
||||||
fi
|
fi
|
||||||
done 4<<< "$(egrep -v '^(#|$)' "${CFGFILE}")" # graciously overlook any comments or blank lines
|
done 4<<< "$(egrep -v '^(#|$)' "${CFGFILE}")" # graciously overlook any comments or blank lines
|
||||||
|
|
Loading…
Reference in New Issue