mirror of
https://github.com/unraid/api.git
synced 2026-01-03 23:19:54 -06:00
Feat: Flash Backup requires connection to mothership (#868)
* fix: branding * feat: flash backup requires connection to mothership * feat: flash backup requires connection to mothership
This commit is contained in:
205
plugin/source/dynamix.unraid.net/etc/rc.d/rc.flash_backup
Executable file
205
plugin/source/dynamix.unraid.net/etc/rc.d/rc.flash_backup
Executable file
@@ -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
|
||||
182
plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api
Executable file
182
plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api
Executable file
@@ -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
|
||||
@@ -522,7 +522,7 @@ _(Allow Remote Access)_:
|
||||
<?if(!$isRegistered): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until you have signed in)_</span></dd></dl>
|
||||
<?elseif(!$isMiniGraphConnected && $myServersFlashCfg['remote']['wanaccess']!="yes"): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud)_</span></dd></dl>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud - try reloading the page)_</span></dd></dl>
|
||||
<?elseif(!$hasMyUnraidNetCert): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until you Provision a myunraid.net SSL Cert)_</span><input type="hidden" id="wanport" value="0"></dd></dl>
|
||||
<?elseif(!$boolWebUIAuth): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
@@ -627,8 +627,8 @@ _(Enable Transparent 2FA for Local Access)_<!-- do not index -->:
|
||||
_(Flash backup)_:
|
||||
<?if(!$isRegistered):?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until you have signed in)_</span>
|
||||
<?elseif(!$isMiniGraphConnected && empty($flashbackup_status['activated'])):?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud)_</span>
|
||||
<?elseif(!$isMiniGraphConnected):?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud - try reloading the page)_</span>
|
||||
<?else: // begin show flash backup form ?>
|
||||
: <span id='flashbackuptext'><span class='blue p0'>_(Loading)_ <i class="fa fa-spinner fa-spin" aria-hidden="true"></i></span></span>
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user