#!/bin/bash # This file is /etc/rc.d/rc.flash_backup # use at queue "f" for flash backup scripts_dir="/usr/local/share/dynamix.unraid.net/scripts" # This loads the API_CONFIG_HOME variable # shellcheck source=../usr/local/share/dynamix.unraid.net/scripts/api_utils.sh source "$scripts_dir/api_utils.sh" QUEUE=" -q f " TASKNAME="/etc/rc.d/rc.flash_backup watch" TASKACTION="/usr/local/emhttp/plugins/dynamix.my.servers/scripts/UpdateFlashBackup update" last=$(date +%s) # set GIT_OPTIONAL_LOCKS=0 globally to reduce/eliminate writes to /boot export GIT_OPTIONAL_LOCKS=0 FAST=1 # 1 second delay when waiting for git SLOW=10 # 10 second delay when waiting for git THIRTYMINS=1800 # 30 minutes is 1800 seconds # wait for existing git commands to complete # $1 is the time in seconds to sleep when waiting. SLOW or FAST _waitforgit() { while [[ $(pgrep -f '^git -C /boot' -c) -ne 0 ]]; do sleep "$1" done } # log to syslog, then wait for existing git commands to complete # $1 is the time in seconds to sleep when waiting. SLOW or FAST _waitforgitlog() { if [[ $(pgrep -f '^git -C /boot' -c) -ne 0 ]]; then logger "waiting for current backup to complete" --tag flash_backup _waitforgit "$1" fi } status() { _connected && CONNECTED="system is connected to Unraid Connect Cloud." || CONNECTED="system is not connected to Unraid Connect Cloud." if _watching; then echo "flash backup monitor is running. ${CONNECTED}" _hasqueue && echo "changes detected, backup queued." exit 0 else if _enabled; then echo "flash backup is enabled but the monitor is not running. ${CONNECTED}" else echo "flash backup is disabled so the monitor is disabled. ${CONNECTED}" fi exit 1 fi } start() { _start exit 0 } stop() { _stop exit 0 } reload() { _start sleep 1 status } _start() { # Note: can start if not signed in, but watcher loop will not process until signed in # only run if flash_backup is enabled if ! _enabled; then logger "flash backup disabled, exiting" --tag flash_backup exit 1 fi _stop # start watcher loop as background process exec ${TASKNAME} &>/dev/null & } _stop() { if _watching; then logger "stop watching for file changes" --tag flash_backup # terminate watcher loop/process pkill --full "${TASKNAME}" &>/dev/null fi # do not flush. better to have unsaved changes than to corrupt the backup during shutdown # note that an existing git process could still be running } flush() { # remove any queued jobs _removequeue # wait for existing git commands to finish before flushing _waitforgitlog "${FAST}" logger "flush: ${TASKACTION}" --tag flash_backup # if _connected, push any changes ad-hoc if _connected; then # shellcheck disable=SC2086 echo "${TASKACTION}_nolimit &>/dev/null" | at ${QUEUE} -M now &>/dev/null fi } _watching() { local flash_backup_pid flash_backup_pid=$(pgrep --full "${TASKNAME}") if [[ ${flash_backup_pid} ]]; then return 0 fi return 1 } _watch() { # safely clean up git *.lock files _clearlocks # flush: this will ensure we start with a clean repo flush # wait for flush to complete sleep 3 _waitforgitlog "${FAST}" logger "checking for changes every $THIRTYMINS seconds" --tag flash_backup # start watcher loop while true; do # if system is connected to Unraid Connect Cloud, see if there are updates to process _connected && _f1 sleep "$THIRTYMINS" done } _f1() { # wait for existing git commands to finish before checking for updates _waitforgit "${SLOW}" if [ "$(git -C /boot status -s)" ]; then _hasqueue || _f2 elif _haserror && _beenawhile; then # we are in an error state and it has been 3 hours since we last tried submitting. run the task now. _runtaskaction fi } _f2() { if ! _haserror || [[ $(($(date +"%M") % 10)) -eq 0 ]]; then logger "adding task: ${TASKACTION}" --tag flash_backup fi sed -i "s@uptodate=yes@uptodate=no@" /var/local/emhttp/flashbackup.ini &>/dev/null _runtaskaction } _hasqueue() { # returns false if the queue is empty, true otherwise # shellcheck disable=SC2086 if [ -z "$(atq ${QUEUE})" ]; then return 1 fi return 0 } _removequeue() { # delete any at jobs in queue f # @TODO shellcheck SC2162 # shellcheck disable=SC2086 atq ${QUEUE} | while read line; do id=$(echo ${line} | cut -d " " -f 1) atrm ${id} done } _runtaskaction() { # shellcheck disable=SC2086 echo "${TASKACTION} &>/dev/null" | at ${QUEUE} -M now +1 minute &>/dev/null last=$(date +%s) } _enabled() { local output output=$(git -C /boot config --get remote.origin.url 2>&1) if [[ "${output}" == *"backup.unraid.net"* ]]; then # Also check if the connect API plugin is enabled if "$scripts_dir/api_utils.sh" is_api_plugin_enabled unraid-api-plugin-connect; then return 0 fi fi return 1 } _connected() { local connect_config username status_cfg connection_status connect_config=$API_CONFIG_HOME/connect.json [[ ! -f "${connect_config}" ]] && return 1 # is the user signed in? username=$(jq -r '.username // empty' "${connect_config}" 2>/dev/null) if [ -z "${username}" ]; then return 1 fi # are we connected to mothership? status_cfg="/var/local/emhttp/connectStatus.json" [[ ! -f "${status_cfg}" ]] && return 1 connection_status=$(jq -r '.connectionStatus // empty' "${status_cfg}" 2>/dev/null) if [[ "${connection_status}" != "CONNECTED" ]]; then return 1 fi return 0 } _haserror() { errorstring=$(awk -F "=" '/error/ {print $2}' /var/local/emhttp/flashbackup.ini 2>&1 || echo '') if [ ${#errorstring} -le 2 ]; then return 1 fi return 0 } _beenawhile() { now=$(date +%s) age=$((now - last)) maxage=$((3 * 60 * 60)) # three hours [[ $age -gt $maxage ]] && return 0 return 1 } # wait for git commands to end, then delete any stale lock files _clearlocks() { _waitforgitlog "${FAST}" find /boot/.git -type f -name '*.lock' -delete } case "$1" in 'status') status ;; 'start') start ;; 'stop') stop ;; 'reload') reload ;; 'flush') flush ;; 'watch') _watch ;; *) echo "usage $0 status|start|stop|reload|flush" ;; esac