diff --git a/plugin/source/dynamix.unraid.net/etc/rc.d/rc.flash_backup b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.flash_backup new file mode 100755 index 000000000..6093c5f51 --- /dev/null +++ b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.flash_backup @@ -0,0 +1,205 @@ +#!/bin/bash +# This file is /etc/rc.d/rc.flash_backup +# use at queue "f" for flash backup +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 +# 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 + # push any changes ad-hoc + # shellcheck disable=SC2086 + echo "${TASKACTION}_nolimit &>/dev/null" | at ${QUEUE} -M now &>/dev/null +} +_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 + logger "start watching for file changes" --tag flash_backup + # start watcher loop + while true; do + # wait for system to be connected to Unraid Connect Cloud, then process flash backups + _connected && _f1 + sleep 60 + 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 + return 0 + fi + return 1 +} +_connected() { + CFG=/var/local/emhttp/myservers.cfg + [[ ! -f "${CFG}" ]] && return 1 + # shellcheck disable=SC1090 + source <(sed -nr '/\[connectionStatus\]/,/\[/{/minigraph/p}' "${CFG}" 2>/dev/null) + if [[ -z "${minigraph}" || "${minigraph}" != "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 diff --git a/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api new file mode 100755 index 000000000..f80837ba8 --- /dev/null +++ b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api @@ -0,0 +1,182 @@ +#!/bin/bash +# unraid-api-handler +flash="/boot/config/plugins/dynamix.my.servers" +[[ ! -d "${flash}" ]] && echo "Please reinstall the Unraid Connect plugin" && exit 1 +[[ ! -f "${flash}/env" ]] && echo 'env=production' >"${flash}/env" +# define env to avoid shellcheck SC2154. Will be overridden by the source command below +env=production +# shellcheck disable=SC1091 +source "${flash}/env" +api_base_directory="/usr/local/bin" + +# Only allow specific envs +if [ "${env}" != "staging" ] && [ "${env}" != "production" ]; then + echo "\"${env}\" is an unsupported env. Please use \"staging\" or \"production\"." + exit 1 +fi + +switchenv() { + stop + # Get current environment from file + local envFile="${flash}/env" + local currentEnv + currentEnv=$( + # shellcheck disable=SC1090 + source "${envFile}" + echo "${env}" + ) + + if [[ "${currentEnv}" = "production" ]]; then + echo "Switching from production to staging" + echo 'env="staging"' >"${envFile}" + cp "${api_base_directory}/unraid-api/.env.staging" "${api_base_directory}/unraid-api/.env" + elif [[ "${currentEnv}" = "staging" ]]; then + echo "Switching from staging to production" + echo 'env="production"' >"${envFile}" + cp "${api_base_directory}/unraid-api/.env.production" "${api_base_directory}/unraid-api/.env" + fi + echo "Run \"unraid-api start\" to start the API." +} +raiseloglevel() { + kill -s SIGUSR2 "$(pidof unraid-api)" +} +lowerloglevel() { + kill -s SIGUSR1 "$(pidof unraid-api)" +} +status() { + LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" status +} +start() { + LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" start 2>&1 | logger & +} +report() { + LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" report "$1" "$2" +} +startdebug() { + LOG_CONTEXT=true LOG_STACKTRACE=true LOG_TRACING=true LOG_LEVEL=debug "${api_base_directory}/unraid-api/unraid-api" start --debug +} +stop() { + LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" stop 2>/dev/null +} +reload() { + LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" restart +} +_install() { + # process file from commandline + if [[ -n "$1" ]]; then + file=$(realpath "${flash}/$1") + if [[ "${file}" == "${flash}"* ]] && [[ "${file}" == *".tgz" || "${file}" == *".zip" ]] && [[ -f "${file}" ]]; then + [[ "${file}" == *".tgz" ]] && ext=tgz || ext=zip + echo "installing $1" + cp "${file}" "${flash}/unraid-api.${ext}" + else + echo "invalid installation file: $1" + exit 1 + fi + fi + + # If this was downloaded from a Github action it'll be a zip with a tgz inside + # Let's extract the tgz and rename it for the next step + if [[ -f "${flash}/unraid-api.zip" ]]; then + for f in ${flash}/unraid-api.zip; do unzip -p "${f}" >"${flash}/${f%.zip}.tgz"; done + rm -f "${flash}/unraid-api.zip" + fi + + # Ensure installation tgz exists + [[ ! -f "${flash}/unraid-api.tgz" ]] && echo "Please reinstall the Unraid Connect plugin" && exit 1 + + # Stop old process + [[ -f "${api_base_directory}/unraid-api/unraid-api" ]] && stop + + # Install unraid-api + rm -rf "${api_base_directory}/unraid-api" + mkdir -p "${api_base_directory}/unraid-api" + tar -C "${api_base_directory}/unraid-api" -xzf "${flash}/unraid-api.tgz" --strip 1 + + # Reset permissions + rm -f "${flash}/data/permissions.json" + + # Copy env file + cp "${api_base_directory}/unraid-api/.env.${env}" "${api_base_directory}/unraid-api/.env" + + # Copy wc files from flash + if [ -f "${flash}/webComps/unraid.min.js" ]; then + rm -rf /usr/local/emhttp/webGui/webComps + mkdir -p /usr/local/emhttp/webGui/webComps + cp ${flash}/webComps/* /usr/local/emhttp/webGui/webComps + else + # not fatal, previous version of unraid.min.js should still exist in /usr/local/emhttp/webGui/webComps + echo "Note: ${flash}/webComps/unraid.min.js is missing" + fi + + # bail if expected file does not exist + [[ ! -f "${api_base_directory}/unraid-api/unraid-api" ]] && echo "unraid-api install failed" && exit 1 +} +install() { + # Install the files + _install "$1" + + # if nginx is running, start the api. if not, it will be started by rc.nginx + if /etc/rc.d/rc.nginx status &>/dev/null; then + # Start new process + start + # Note: do not run another unraid-api command until you see "UNRAID API started successfully!" in syslog + sleep 3 + echo "unraid-api installed and started" + else + echo "unraid-api installed" + fi + exit 0 +} +uninstall() { + # Stop old process + [[ -f "${api_base_directory}/unraid-api/unraid-api" ]] && stop + + # Remove all unraid-api files + rm -rf "${api_base_directory}/unraid-api" + rm -f /var/run/unraid-api.sock +} +case "$1" in +'status') + status + ;; +'start') + start + ;; +'report') + report "$2" "$3" + ;; +'switch-env') + switchenv + ;; +'start-debug') + startdebug + ;; +'raise-log-level') + raiseloglevel + ;; +'lower-log-level') + lowerloglevel + ;; +'stop') + stop + ;; +'reload') + reload + ;; +'restart') + reload + ;; +'install') + install "$2" + ;; +'_install') + _install "$2" + ;; +'uninstall') + uninstall + ;; +*) + echo "usage $0 status|start|report|switch-env|start-debug|raise-log-level|lower-log-level|stop|reload|install|uninstall" + ;; +esac diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page index 8f66ebc9c..a6e70f557 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page @@ -522,7 +522,7 @@ _(Allow Remote Access)_: : _(Disabled until you have signed in)_ -: _(Disabled until connected to Unraid Connect Cloud)_ +: _(Disabled until connected to Unraid Connect Cloud - try reloading the page)_ : _(Disabled until you Provision a myunraid.net SSL Cert)_ @@ -627,8 +627,8 @@ _(Enable Transparent 2FA for Local Access)_: _(Flash backup)_: : _(Disabled until you have signed in)_ - -: _(Disabled until connected to Unraid Connect Cloud)_ + +: _(Disabled until connected to Unraid Connect Cloud - try reloading the page)_ : _(Loading)_ diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php index 613dcbcc1..2c42f38a1 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php @@ -70,8 +70,8 @@ function save_flash_backup_state($loading='') { rename($flashbackup_tmp, $flashbackup_ini); } -function load_flash_backup_state() { - global $arrState,$flashbackup_ini,$isRegistered; +function default_flash_backup_state() { + global $arrState; $arrState = [ 'activated' => 'no', @@ -80,6 +80,12 @@ function load_flash_backup_state() { 'error' => '', 'remoteerror' => '' ]; +} + +function load_flash_backup_state() { + global $arrState,$flashbackup_ini,$isRegistered; + + default_flash_backup_state(); $arrNewState = (file_exists($flashbackup_ini)) ? @parse_ini_file($flashbackup_ini) : []; if ($arrNewState) { @@ -277,7 +283,14 @@ if ($pgrep_output[0] != "0") { // check if signed-in if (!$isRegistered) { - response_complete(406, array('error' => 'Must be signed in to My Servers to use Flash Backup')); + default_flash_backup_state(); + response_complete(406, array('error' => 'Must be signed in to Unraid Connect to use Flash Backup')); +} + +// check if connected to Unraid Connect Cloud +if (!$isConnected) { + default_flash_backup_state(); + response_complete(406, array('error' => 'Must be connected to Unraid Connect Cloud to use Flash Backup')); } // keyfile @@ -540,7 +553,7 @@ if (empty($SSH_PORT)) { } else { $arrState['loading'] = ''; if (stripos(implode($ssh_output),'permission denied') !== false) { - $arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to My Servers Cloud'; + $arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to Unraid Connect Cloud'; } else { $arrState['error'] = 'Unable to connect to backup.unraid.net:22'; } @@ -654,7 +667,7 @@ if ($command == 'update' || $command == 'activate') { if ($return_var != 0) { // check for permission denied if (stripos(implode($push_output),'permission denied') !== false) { - $arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to My Servers Cloud'; + $arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to Unraid Connect Cloud'; } elseif (stripos(implode($push_output),'fatal: loose object') !== false && stripos(implode($push_output),'is corrupt') !== false) { // detect corruption #2 $arrState['error'] = 'Error: Backup corrupted';