/usr/sbin/simplesnap is in simplesnap 1.0.3.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
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 | #!/bin/bash
# Simple snapshot manager
# Copyright (c) 2014 John Goerzen
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set -e
# Log a message
logit () {
logger -p info -t "`basename $0`[$$]" "$1"
}
# Log stdin with the given code. Used normally to log stderr.
logstdin () {
logger -p info -t "`basename $0`[$$/$1]"
}
# Run command, logging stderr and exit code
runcommand () {
logit "Running $*"
if "$@" 2> >(logstdin "$1") ; then
logit "$1 exited successfully"
return 0
else
RETVAL="$?"
logit "$1 exited with error $RETVAL"
return "$RETVAL"
fi
}
exiterror () {
logit "$1"
echo "$1" 1>&2
exit 10
}
syntaxerror () {
cat <<EOF
Syntax:
$0 [--sshcmd CMD] [--local] [--wrapcmd CMD]
[--backupdataset DATASET [--datasetdest DEST]]
--store STORE --setname NAME --host HOST
or
$0 --check TIMEFRAME --store STORE --setname NAME [--host HOST]
Required:
--store: gives the ZFS dataset name where data will be stored. Mountpoint
is inferred.
--setname: Gives the backup set name. Can just be a made-up word
if multiple sets aren not needed; for instance, the hostname
of the backup server. This is used in the snapshot name.
--host: Gives the hostname to back up.
Optional:
--sshcmd: Gives the SSH command. Defaults to "ssh". Example:
--sshcmd "ssh -i /root/.id_rsa_simplesnap"
--wrapcmd: Gives the path to simplesnapwrap on the remote
(or local machine, if --local is present).
Not usually relevant, since this is set in
~/.ssh/authorized_keys. Default: "simplesnapwrap"
--local: The host is localhost; do not use remote tool to access it.
--backupdataset: Back up only the specified dataset instead of all.
--datasetdest: Valid only with --backupdataset; store the backup in
the specified location.
--check: Do not back up, but check existing backups. If any are
older than TIMEFRAME, print an error and exit with a nonzero
code. Scans all hosts unless a specific host is given with
--host. The parameter is in the format given to date/gdate:
for instance, --check "30 days ago". Remember to enclose it
in quotes if it contains spaces.
EOF
exiterror "Syntax error: $1"
}
logit "Invoked as: $0 $*"
while [ -n "$1" ]; do
case "$1" in
"--sshcmd")
SSHCMD="$2"
shift 2
;;
"--wrapcmd")
WRAPCMD="$2"
shift 2
;;
"--store")
STORE="$2"
shift 2
;;
"--setname")
SETNAME="$2"
shift 2
;;
"--host")
HOST="$2"
shift 2
;;
"--backupdataset")
BACKUPDATASET="$2"
shift 2
;;
"--datasetdest")
DATASETDEST="$2"
shift 2
;;
"--check")
CHECKMODE="$2"
if [ -z "$CHECKMODE" ] ; then
syntaxerror "--check requires a paremter"
fi
shift 2
;;
"--local")
LOCALMODE="on"
shift
;;
*)
syntaxerror "Unknown option \"$1\""
;;
esac
done
SSHCMD="${SSHCMD:-ssh}"
WRAPCMD="${WRAPCMD:-simplesnapwrap}"
DATE="gdate"
gdate &> /dev/null || DATE="date"
SED="gsed"
gsed &> /dev/null || SED="sed"
GREP="ggrep"
ggrep &> /dev/null || GREP="grep"
# Validating
[ -n "$SSHCMD" ] || syntaxerror "Invalid SSH command: $SSHCMD"
[ -n "$STORE" ] || syntaxerror "Missing --store"
[ -n "$SETNAME" ] || syntaxerror "Missing --setname"
[ -n "$CHECKMODE" -o -n "$HOST" ] || syntaxerror "Missing --host"
echo "_${STORE}" | $GREP -qv " " || syntaxerror "Space in --store: ${STORE}"
TEMPLATEPATTERN="^[a-zA-Z0-9]\+\$"
echo "a${SETNAME}" | $GREP -q "${TEMPLATEPATTERN}" || syntaxerror "Invalid characters in setname \"${SETNAME}\"; pattern is \"${TEMPLATEPATTERN}\""
echo "_${SETNAME}" | $GREP -qv '^_-' || syntaxerror "Set name cannot begin with a dash: \"${SETNAME}\""
TEMPLATE="__simplesnap_${SETNAME}_"
[ -n "$DATASETDEST" -a -z "$BACKUPDATASET" ] && syntaxerror "--datasetdest given without --backupdataset"
MOUNTPOINT="`zfs list -H -o mountpoint -t filesystem \"${STORE}\"`"
logit "Store ${STORE} is mounted at ${MOUNTPOINT}"
cd "${MOUNTPOINT}"
# template - $1
# dataset - $2
listsnaps () {
runzfs list -t snapshot -r -d 1 -H -o name "$2" | $GREP "@$1" || true
}
CHECKRETVAL=0
runzfs () {
runcommand /sbin/zfs "$@"
}
checkbackups () {
CHECKHOST="$1"
DATASETS="`runzfs list -t filesystem,volume -o name -H -r \"${STORE}/${HOST}\"`"
CUTOFF="`$DATE -d \"${CHECKMODE}\" +%s`"
for CHECKDS in ${DATASETS}; do
# Don't check the top-level host dataset itself.
if [ "${CHECKDS}" = "${STORE}/${HOST}" ]; then
continue
fi
FOUNDOK=0
# Extract timestamps
for TIMESTAMP in `listsnaps "$TEMPLATE" "$CHECKDS" | $SED 's/^.*_\([^_]\+\)__$/\1/'`; do
TSSEC=`$DATE -d "${TIMESTAMP}" +%s`
if [ "$TSSEC" -gt "$CUTOFF" ];
then FOUNDOK=1
fi
done
if [ "$FOUNDOK" = "0" ]; then
echo "${CHECKDS} last back up is too old; created at `$DATE -d \"@${TSSEC}\"` but cutoff is `$DATE -d \"@${CUTOFF}\"`!" 1>&2
CHECKRETVAL=10
fi
done
}
if [ -n "$CHECKMODE" ]; then
# Do a check only.
if [ -n "$HOST" ]; then
checkbackups "$HOST"
else
for HOST in *; do checkbackups "$HOST"; done
fi
logit "check: exiting with value $CHECKRETVAL"
exit $CHECKRETVAL
fi
runwrap () {
if [ "$LOCALMODE" = "on" ]; then
runcommand "${WRAPCMD}" reinvoked simplesnapwrap "$@"
else
runcommand ${SSHCMD} ${HOST} ${WRAPCMD} "$@"
fi
}
if [ ! -d "${MOUNTPOINT}/${HOST}" ]; then
runzfs create "${STORE}/${HOST}"
fi
LOCKFILE="${MOUNTPOINT}/${HOST}/.lock"
if dotlockfile -r 0 -l -p "${LOCKFILE}"; then
LOCKMETHOD="dotlockfile"
logit "Lock obtained at ${LOCKFILE} with dotlockfile"
trap "ECODE=$?; dotlockfile -u \"${LOCKFILE}\"; exit $ECODE" EXIT INT TERM
else
RETVAL="$?"
if [ "$RETVAL" = "127" ]; then
LOCKMETHOD="mkdir"
mkdir "${LOCKFILE}" || exiterror "Could not obtain lock at ${LOCKFILE}; if $0 is not already running, rmdir that path."
logit "Lock obtained at ${LOCKFILE} with mkdir"
trap "ECODE=$?; rmdir \"${LOCKFILE}\"" EXIT INT TERM
else
exiterror "Could not obtain lock at ${LOCKFILE}; $0 likely already running."
fi
fi
reap () {
DATASET="$1"
# We always save the most recent.
SNAPSTOREMOVE="`listsnaps \"${TEMPLATE}\" \"${DATASET}\" | head -n -1`"
if [ -z "${SNAPSTOREMOVE}" ]; then
logit "No snapshots to remove."
else
for REMOVAL in ${SNAPSTOREMOVE}; do
logit "Destroying snapshot ${REMOVAL}"
echo "_${REMOVAL}" | $GREP -q '@' || exiterror "PANIC: snapshot name doesn not contain @"
runzfs destroy "${REMOVAL}"
done
fi
}
backupto() {
DATASET="$1"
DESTDIR="$2"
DATASETPATTERN="^[a-zA-Z0-9_/.-]\+\$"
if echo "a$DATASET" | $GREP -vq "$DATASETPATTERN"; then
logit "Dataset \"$DATASET\" contains invalid characters; pattern is $DATASETPATTERN"
return
fi
if echo "a$DATASET" | $GREP -q "^[/-]"; then
exiterror "Dataset \"$DATASET\" begins with a / or a -; something is wrong. Aborting."
fi
if echo "a$DATASET" | $GREP -q '\.\.'; then
exiterror "Dataset \"$DATASET\" contains ..; something is wrong. Aborting."
fi
if runwrap sendback "${SETNAME}" "${DATASET}" | \
runzfs receive -F "${DESTDIR}"; then
logit "Received backup into ${DESTDIR}"
runwrap reap "${SETNAME}" "${DATASET}"
reap "${DESTDIR}"
else
logit "zfs receive died with error: $?"
exit 100
fi
}
# If the user requested only one dataset to be backed up:
if [ -n "${BACKUPDATASET}" ]; then
logit "Option --backupdataset ${BACKUPDATASET} requested; not asking remote for dataset list."
# If the user gave a specified location:
if [ -n "${DATASETDEST}" ]; then
backupto "${BACKUPDATASET}" "${DATASETDEST}"
else
backupto "${BACKUPDATASET}" "${STORE}/${HOST}/${BACKUPDATASET}"
fi
else
logit "Finding remote datasets to back up"
REMOTEDATASETS="`runwrap listfs`"
for DATASET in ${REMOTEDATASETS}; do
backupto "${DATASET}" "${STORE}/${HOST}/${DATASET}"
done
fi
logit "Exiting successfully."
|