Skip to content

Instantly share code, notes, and snippets.

@af3556
Last active February 7, 2025 01:02
Show Gist options
  • Save af3556/331f3e52c289a5cbeab44dd945b3d487 to your computer and use it in GitHub Desktop.
Save af3556/331f3e52c289a5cbeab44dd945b3d487 to your computer and use it in GitHub Desktop.
scripts to initiate a backup from a source Synology NAS
#!/bin/sh
# this script will initiate a backup from a source Synology NAS
# 'challenges'
# - there's no blocking means to run a backup task (i.e. a command to start a
# backup task and not return until it's complete); instead we only have
# asynchronous actions to start a task and then later check state
# - it takes some period of time to actually start the task, i.e. starting a
# backup and immediately checking backup state will likely (incorrectly)
# return "not running"
# - there appears to be no way to actually tell if the backup succeeded or not,
# other than inspecting the log file (sigh)
# - root (sudo) is required to read journal logs; the user this script runs as
# needs to be in the `wheel` group so as to be able to sudo without a password
# (ref. /etc/sudoers), e.g. for the user `offsite`:
# synogroup --memberadd wheel offsite
# (will also need to be in `administrators` in order to ssh)
#
# - the synobackup utility requires the 'task id', however there is no simple
# way of determining the task ID for a task created via the UI
# - prior to Hyperbackup 4.1.0 the task ID was listed in
# /usr/syno/etc/synobackup.conf ('ini' format, unparseable)
# however that file no longer exists and the task ID appears to be
# indeterminable.
# - fortunately it appears to be simply an index starting from 1,
# incremented whenever a new task is created; moreover passing an invalid
# task id appears to have no harm
# i.e. invoke synobackup with a task ID of 1, increment until you see
# (in the UI) the job you want to run actually start
TASK_ID="$1"
HOSTNAME="$(hostname)"
log () {
echo "$(date) [${HOSTNAME}] $*"
}
if [ $# -eq 0 ]; then
echo "usage: $0 <task id>" >&2
exit 1
fi
# synobackup does not provide any means to check whether a backup worked (e.g.
# result of last backup); have to resort to monitoring the log...
# examples:
# - partial failure (source folder not mounted):
# Jan 20 10:28:19 bd synobackupd[6784]: launch job [3] on pid [23763]
# Jan 20 10:56:24 bd img_backup[23763]: (23763) [err] backup_progress.cpp:501 Backup task [offsite - general] completes with result [2] and errorcode [3004]. Time spent: [1684 sec].
# - success
# Jan 20 10:58:39 bd synobackupd[6784]: launch job [4] on pid [28621]
# Jan 20 11:00:54 bd img_backup[28621]: (28621) [err] backup_progress.cpp:504 Backup task [offsite - photo] completes with result [1]. Time spent: [132 sec].
# - don't want the whole log, only what's added during our run
now=$(date +%s)
getsynolog () {
sudo journalctl --no-pager --unit synobackupd.service --since "@$now"
}
_exit() {
log "exiting"
getsynolog | grep --quiet --fixed-strings 'completes with result [1]'
rc=$?
if [ $rc -ne 0 ]; then
log "failed?"
fi
log "synobackupd log:"
getsynolog
exit "$rc"
}
trap _exit EXIT
# no sudo, plain ol' admins can kick off a backup...
/usr/syno/bin/synobackup --backup "${TASK_ID}" --type image
case $? in
0)
log "backup task ID ${TASK_ID} started"
;;
153)
log "backup task ID ${TASK_ID} already running?"
exit 1
;;
*)
log "failed to start backup task ID ${TASK_ID}"
exit 1
;;
esac
is_backup_running() {
# synobackup's exit status is backwards:
# 0 (i.e. success/true) if not running
# 1 (i.e. error/false) if running
if /usr/syno/bin/synobackup --is-backup-restore-running; then
return 1
fi
return 0
}
# can take a moment before synobackup reports the task state, so sleep up front
# "give up" logic: things don't always go to plan:
# - wait till the backup ends; increasing intervals, to a total of ~30 hrs
for s in $(seq 90 15 1800) -1; do
if [ "${s}" -lt 0 ]; then
log "timed out: backup still running??"
exit 2
fi
log "waiting ${s}s"
sleep "$s"
if ! is_backup_running; then
log "backup finished running"
break
fi
done
#!/bin/sh
# this script will initiate a backup from a source Synology NAS
# - before deploying this script via the Synology task scheduler: the backup
# source's ssh host key needs to be recorded in ~/.ssh/known_hosts - to do
# so, as the user that will run this script in the Synology task scheduler,
# invoke this script with only the user@host argument, e.g.
# sudo ./pull_backup.sh [email protected]
# - if running via the 'Boot-up' Task Scheduler event it will be beneficial to precede
# invocation of this script with a `sleep 180` otherwise you may get spurious
# networking errors
# - this script needs to be run as root so as to be able to call shutdown
# - invoke with arg1 as target username@host; arg2 as the backup script on the target
# - in turn, the backup script requires the synobackup task ID
# e.g.
# sudo $0 [email protected] ./do_backup.sh 3
HOSTNAME="$(hostname)"
log () {
echo "$(date) [${HOSTNAME}] $*"
}
ssh_with_args() {
# adjust as needed
ssh "$SSH_TARGET" \
-o ControlMaster=auto -o ControlPersist=5m -o ControlPath=~/.ssh/cm-%r@%h:%p \
"$@"
}
if [ $# -eq 0 ]; then
echo "usage: $0 user@host dobackup_script [dobackup_script args]" >&2
exit 1
fi
SSH_TARGET="$1"
shift
# if invoked with just one argument, do a "ssh host key scan" and exit
# note: synology doesn't provide ssh-keyscan...
if [ $# -eq 0 ]; then
ssh_with_args -o StrictHostKeyChecking=no exit
exit
fi
# shutdown will be suppressed if, when the backup ends:
# - anyone is logged in via ssh
# - this file is present:
NOSHUTDOWN="$0.noshutdown"
do_shutdown() {
# return true if shutdown should proceed
[ -e "${NOSHUTDOWN}" ] && return 1
who --short | grep -q '.' && return 1
return 0
}
# can take a moment for ssh to get going (e.g. VPN/tunnel to establish)
for s in $(seq 30 15 90) -1; do
if [ "${s}" -lt 0 ]; then
log "failed to connect to $SSH_TARGET"
exit 1
fi
if ssh_with_args true; then
log "connected to $SSH_TARGET"
break
fi
log "waiting to connect"
sleep "$s"
done
log "executing $@"
if ! ssh_with_args "$@"; then
log "failed"
exit 1
fi
log "success"
# shutdown ignores arguments; certainly time; so DIY
# this is important: need to provide opportunity to fix things
if do_shutdown; then
log "shutting down"
# leave the pending shutdown run in the background, so as to let any email report/etc go out
nohup sh -c 'sleep 300 && shutdown --poweroff' >/dev/null &
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment