]> ##&name; ###&version; - initial release /dev/null /etc/rc.d/rc.unraid-api uninstall rm -f /etc/rc.d/rc.unraid-api rm -f /etc/rc.d/rc.flash_backup mv -f /usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php- /usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php mv -f /usr/local/emhttp/plugins/dynamix/Registration.page- /usr/local/emhttp/plugins/dynamix/Registration.page mv -f /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php- /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php mv -f /usr/local/emhttp/plugins/dynamix/DisplaySettings.page- /usr/local/emhttp/plugins/dynamix/DisplaySettings.page rm -rf /boot/config/plugins/Unraid.net/wc rm -f /boot/config/plugins/Unraid.net/unraid-api.tgz rm -f /boot/config/plugins/Unraid.net/.gitignore rm -rf /usr/local/emhttp/plugins/dynamix.unraid.net rm -f /usr/local/emhttp/webGui/javascript/vue.js rm -f /usr/local/emhttp/webGui/javascript/vue.min.js rm -rf /usr/local/emhttp/webGui/wc find /usr/local/emhttp/languages -type f \( -iname unraidnet.txt -o -iname unraidnet.dot \) -delete exit 0 fi ]]> ]]> &unraid-api; if [ "&env;" = "production" ] || [ ! -f /boot/config/plugins/Unraid.net/env ]; then echo 'env="&env;"' > /boot/config/plugins/Unraid.net/env fi $envFile cp $node_base_directory/unraid-api/.env.staging $node_base_directory/unraid-api/.env elif [[ $currentEnv = "staging" ]]; then echo "Switching from staging to production" echo 'env="production"' > $envFile cp $node_base_directory/unraid-api/.env.production $node_base_directory/unraid-api/.env fi source $envFile; reload } _start() { _stop local old_working_directory=$(echo $pwd) mkdir -p $node_base_directory cd $node_base_directory # Local .env if [ -f $node_base_directory/unraid-api/.env ]; then # Load Environment Variables export $(egrep -v '^#' $node_base_directory/unraid-api/.env | xargs) fi # Start unraid-api ENVIRONMENT=$(echo $env) LOG_TRANSPORT=console node $node_base_directory/unraid-api/index.js 2>&1 | logger & cd $old_working_directory # wait until node_api_pid exists for i in {1..10}; do local node_api_pid=$(pidof unraid-api | awk '{print $1}') if [ -n "$node_api_pid" ]; then break fi sleep 1 done } start() { echo "Starting Unraid-api" _start status exit 0 } _node_api_version() { # Borrowed from https://gist.github.com/DarrenN/8c6a5b969481725a4413 local version=$(grep '"version"' $node_base_directory/unraid-api/package.json | cut -d '"' -f 4) echo "v$version" } report() { cat << EOF <-----UNRAID-API-REPORT-----> Env $env Node API $(_node_api_version) Unraid v$(source /etc/unraid-version; echo "$version";) EOF } startdebug() { _stop local old_working_directory=$(echo $pwd) mkdir -p $node_base_directory # Local .env if [ -f $node_base_directory/unraid-api/.env ]; then # Load Environment Variables export $(egrep -v '^#' $node_base_directory/unraid-api/.env | xargs) fi # Print report report # Start unraid-api ENVIRONMENT=$(echo $env) DEBUG=true node $node_base_directory/unraid-api/index.js } changeloglevel() { kill -USR2 $(pidof unraid-api) } _stop() { local node_api_pid=$(pidof unraid-api | awk '{print $1}') if [[ $node_api_pid ]]; then local parent_pid=$(cat /proc/$node_api_pid/status | grep PPid | cut -f2) if [[ $parent_pid != 1 ]]; then kill -9 $parent_pid &> /dev/null else kill -9 $node_api_pid &> /dev/null fi # wait until node_api_pid no longer exists for i in {1..10}; do node_api_pid=$(pidof unraid-api | awk '{print $1}') if [ -z "$node_api_pid" ]; then break fi sleep 1 done fi } stop() { echo "Stopping Unraid-api" _stop status exit 0 } reload() { echo "Reloading Unraid-api" _stop rm -f /var/run/unraid-api.sock _start status exit 0 } _install() { # Install unraid-api for download in ${downloads[@]}; do rm -rf $node_base_directory/${download} mkdir -p $node_base_directory/${download} tar -C $node_base_directory/${download} -xzf /boot/config/plugins/Unraid.net/${download}.tgz --strip 1 done # Reset permissions rm -f /boot/config/plugins/Unraid.net/data/permissions.json # Copy env file cp $node_base_directory/unraid-api/.env.$env $node_base_directory/unraid-api/.env # Copy across wc files rm -rf /usr/local/emhttp/webGui/wc mkdir -p /usr/local/emhttp/webGui/wc cp /boot/config/plugins/Unraid.net/wc/* /usr/local/emhttp/webGui/wc } install() { # Stop old process _stop # Install the files _install # Start new process _start # Wait for inital process to boot sleep 2 # Print status and exit status exit 0 } uninstall() { stop for download in ${downloads[@]}; do rm -rf $node_base_directory/${download} done rm -f /var/run/unraid-api.sock } case "$1" in 'status') status ;; 'start') start ;; '_start') _start ;; 'report') report ;; 'switch-env') switchenv ;; 'start-debug') startdebug ;; 'change-log-level') changeloglevel ;; 'stop') stop ;; 'reload') reload ;; 'install') install ;; '_install') _install ;; 'uninstall') uninstall ;; *) echo "usage $0 status|start|report|switch-env|start-debug|change-log-level|stop|reload|install|uninstall" esac ]]> **Unraid.net** Unraid.net provides access to a set web-based services: * Server status such as online/offline, storage used/available, etc. * Links for local and remote access to your server webGUI. * Backup and Restore of your USB Flash boot device. * much more to come A server is registered using your Unraid Community Forum credentials. Registered servers appear under the My Servers forum sub-menu. This is work in progress. Use this for testing purposes only! Der WAN-Netzwerkport ist die externe TCP-Portnummer, die auf Ihrem Router für NAT / Port eingerichtet wurde. > Leiten Sie den Datenverkehr vom Internet an diesen weiter Der SSL-Netzwerkport des unRAID-Servers für sicheren Webverkehr :end :unraidnet_inactivespanel_help: > Klicken Sie auf Aktivieren, um ein lokales Git-Repo für Ihr lokales USB-Flash-Startgerät einzurichten und eine Verbindung zu einer dedizierten Fernbedienung auf unraid.net herzustellen, die an diesen Server gebunden ist. :end :unraidnet_changespanel_help: > Der Status "Nicht auf dem neuesten Stand" zeigt an, dass lokale Dateien im Vergleich zur Fernbedienung auf unraid.net geändert wurden. > Klicken Sie auf Aktualisieren, um Änderungen auf den Remote-Server zu übertragen. > Klicken Sie auf Änderungen, um zu sehen, was sich geändert hat. :end :unraidnet_uptodatepanel_help: > Der Status "auf dem neusten Stand" zeigt an, dass Ihre lokale Konfiguration mit der auf der unraid.net-Fernbedienung gespeicherten übereinstimmt. :end :unraidnet_activepanel_help: > Klicken Sie auf Deaktivieren, um die Kommunikation mit Ihrer Fernbedienung auf unraid.net zu unterbrechen. > Klicken Sie auf Neu initialisieren, um den gesamten Änderungsverlauf sowohl auf dem lokalen als auch auf dem unraid.net-Remoteserver zu löschen. :end ]]> Le port WAN est la configuration du numéro de port TCP externe sur votre routeur vers le trafic NAT / Port Forward d'Internet vers ce > Port SSL du serveur Unraid pour un trafic Web sécurisé. :end :unraidnet_inactivespanel_help: > Cliquez sur Activer pour configurer un référentiel git local pour votre périphérique de démarrage USB Flash local et connectez-vous à une télécommande dédiée sur unraid.net liée à ce serveur. :end :unraidnet_changespanel_help: > L'état "Non à jour" indique que des fichiers locaux sont modifiés par rapport au serveur distant sur le serveur unraid.net. > Cliquez sur "Mettre à jour" pour envoyer les modifications au serveur distant. > Cliquez sur "Modifications" pour voir ce qui a changé. :end :unraidnet_uptodatepanel_help: > L'état "À jour" indique les correspondances de votre configuration locale avec celles stockées sur le serveur distant unraid.net. :end :unraidnet_activepanel_help: > Cliquez sur Désactiver pour interrompre la communication avec votre télécommande sur unraid.net. > Cliquez sur Réinitialiser pour effacer tout l'historique des modifications dans la télécommande locale et unraid.net :end ]]> El puerto WAN es la configuración del número de puerto TCP externo en su enrutador para el tráfico de reenvío de puerto / NAT desde Internet a este > Puerto SSL del servidor Unraid para un tráfico web seguro. :end :unraidnet_inactivespanel_help: > Haga clic en Activar para configurar un repositorio de git local para su dispositivo de arranque flash USB local y conéctese a un control remoto dedicado en unraid.net vinculado a este servidor. :end :unraidnet_changespanel_help: > El estado "No actualizado" indica que hay archivos locales que se cambiaron en comparación con la copia de seguridad remota en unraid.net. > Haga clic en "Actualizar" para enviar los cambios al servidor remoto. > Haga clic en "Cambios" para ver qué ha cambiado. :end :unraidnet_uptodatepanel_help: > El estado "actualizado" indica que su configuración local coincide con la almacenada en el servidor remoto de unraid.net. :end :unraidnet_activepanel_help: > Haga clic en "Desactivar" para pausar la comunicación con su copia de seguridad remota en unraid.net. > Haga clic en "Reinicializar" para borrar todo el historial de cambios en el servidor local y remoto de Unraid.net. :end ]]> WAN Port is the external TCP port number setup on your router to NAT/Port Forward traffic from the internet to this > Unraid server SSL port for secure web traffic. :end :unraidnet_inactivespanel_help: > Click Activate to set up a local git repo for your local USB Flash boot device and connect to a dedicated remote on unraid.net tied to this server. :end :unraidnet_changespanel_help: > The Not Up-to-date status indicates there are local files which are changed vs. the remote on unraid.net. > Click Update to push changes to the remote. > Click Changes to see what has changed. :end :unraidnet_uptodatepanel_help: > The Up-to-date status indicates your local configuration matches that stored on the unraid.net remote. :end :unraidnet_activepanel_help: > Click Deactivate to pause communication with your remote on unraid.net. > Click Reinitialize to erase all change history in both local and unraid.net remote. :end ]]> "", "wanaccess" => "no", "wanport" => "443" ]; } if (empty($remote['wanport'])) { $remote['wanport'] = 443; } $hasCert = $var['USE_SSL']!='no' && file_exists('/boot/config/ssl/certs/certificate_bundle.pem') && preg_match('/[0-9a-f]{40}\.unraid\.net$/', exec('openssl x509 -in /boot/config/ssl/certs/certificate_bundle.pem -subject -noout 2>&1')); $isRegistered = !empty($remote['apikey']); $boolWebUIAuth = $isRegistered && file_exists('/etc/nginx/htpasswd'); ?>
_(Unraid.net Status)_: : _(Allow Remote Access)_: : _(Disabled until you Provision an unraid.net SSL Cert and set SSL-TLS to Auto)_ : _(Disabled until your root user account is password-protected)_ :
_(WAN Port)_: : :unraidnet_wanpanel_help:
  :
_(Flash backup)_: : _(Loading)_
]]>
mv -f /usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php /usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php- "", "wanaccess" => "no", "wanport" => "443" ]; } if (empty($remote['wanport'])) { $remote['wanport'] = 443; } if ($cli) { $remoteaccess = $remote['wanaccess']; $externalport = $remote['wanport']; $username = ''; $password = ''; } else { $remoteaccess = $_POST['remoteaccess']; $externalport = $_POST['externalport']; $username = $_POST['username']; $password = $_POST['password']; } $isRegistered = !empty($remote['apikey']); // protocol, hostname, internalport list($protocol, $hostname, $internalport) = explode(":", rtrim(file_get_contents("/var/run/nginx.origin"))); $hostname = substr($hostname, 2); if (!$isRegistered && empty($username) && !preg_match('/.*\.unraid\.net$/', $hostname)) { response_complete(406, '{"error":"Nothing to do"}'); } // keyfile $var = parse_ini_file('/var/local/emhttp/var.ini'); $keyfile = @file_get_contents($var['regFILE']); if ($keyfile === false) { response_complete(406, '{"error":"Registration key required"}'); } $keyfile = @base64_encode($keyfile); // internalip extract(parse_ini_file('/var/local/emhttp/network.ini',true)); $internalip = $eth0['IPADDR:0']; $ch = curl_init('https://keys.lime-technology.com/account/server/register'); curl_setopt($ch, CURLOPT_POST, 1); if (!$isRegistered && empty($username)) { curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'internalip' => $internalip, 'keyfile' => $keyfile ]); } else { // servername, servercomment $servername = $var['NAME']; $servercomment = $var['COMMENT']; if (empty($username)) { curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'servername' => $servername, 'servercomment' => $servercomment, 'protocol' => $protocol, 'hostname' => $hostname, 'internalport' => $internalport, 'internalip' => $internalip, 'remoteaccess' => $remoteaccess, 'externalport' => $externalport, 'keyfile' => $keyfile ]); } else { curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'servername' => $servername, 'servercomment' => $servercomment, 'protocol' => $protocol, 'hostname' => $hostname, 'internalport' => $internalport, 'internalip' => $internalip, 'remoteaccess' => $remoteaccess, 'externalport' => $externalport, 'username' => $username, 'password' => $password, 'keyfile' => $keyfile ]); } } curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($result === false) { response_complete(500, '{"error":"'.$error.'"}'); } response_complete($httpcode, $result, 'success'); ?> ]]> /dev/null # flush: this will ensure we start with a clean repo flush # start watcher loop as background process exec ${TASKNAME} &>/dev/null & exit 0 } stop() { # terminate watcher loop/process pkill --full "${TASKNAME}" &>/dev/null # remove any queued jobs and flush changes flush exit 0 } reload() { stop sleep 1 start sleep 1 status exit 0 } flush() { # remove any queued jobs _removequeue # push any changes ad-hoc echo "${TASKACTION} &>/dev/null" | at ${QUEUE} now &>/dev/null } _watch() { # start watcher loop while true; do if [ "$(git -C /boot status -s)" ]; then _hasqueue || ( logger "adding task: ${TASKACTION}" --tag flash_backup; echo "${TASKACTION} &>/dev/null" | at ${QUEUE} now +1 minute &>/dev/null ) fi sleep 60; done } _hasqueue() { # returns false if the queue is empty, true otherwise if [ -z "$(atq ${QUEUE})" ]; then return 1 fi return 0 } _removequeue() { # delete any at jobs in queue f atq ${QUEUE} | while read line; do id=`echo ${line} | cut -d " " -f 1` atrm ${id} done } _enabled() { local output=$(git -C /boot config --get remote.origin.url 2>&1) if [[ $output == *"backup.unraid.net"* ]]; then return 0 fi return 1 } case "$1" in 'status') status ;; 'start') start ;; 'stop') stop ;; 'reload') reload ;; 'flush') flush ;; 'watch') _watch ;; *) echo "usage $0 status|start|stop|reload|flush" esac ]]> "", "username" => "", "avatar" => "", "wanaccess" => "no", "wanport" => "443" ]; } function response_complete($httpcode, $result, $cli_success_msg='') { global $cli; save_flash_backup_state(); if ($cli) { $json = @json_decode($result,true); if (!empty($json['error'])) { echo 'Error: '.$json['error'].PHP_EOL; exit(1); } if (!empty($cli_success_msg)) $cli_success_msg .= PHP_EOL; exit($cli_success_msg); } header('Content-Type: application/json'); http_response_code($httpcode); exit((string)$result); } function save_flash_backup_state($loading='') { global $arrState; $arrState['loading'] = $loading; $text = "[flashbackup]\n"; foreach ($arrState as $key => $value) { if ($value === false) $value = 'false'; if ($value === true) $value = 'true'; $text .= "$key=" . $value . "\n"; } file_put_contents('/var/local/emhttp/flashbackup.new', $text); rename('/var/local/emhttp/flashbackup.new', '/var/local/emhttp/flashbackup.ini'); } function load_flash_backup_state() { global $remote; global $arrState; $arrState = [ 'activated' => false, 'uptodate' => false, 'loading' => '' ]; $arrNewState = false; if (file_exists('/var/local/emhttp/flashbackup.ini')) { $arrNewState = parse_ini_file('/var/local/emhttp/flashbackup.ini'); } if ($arrNewState !== false) { $arrState = array_merge($arrState, $arrNewState); } $arrState['activated'] = empty($arrState['activated']) ? true : false; $arrState['uptodate'] = empty($arrState['uptodate']) ? true : false; $arrState['registered'] = !empty($remote['apikey']); } function exec_log($command, &$output = [], &$retval = 0) { try { exec($command.' 2>&1', $output, $retval); if ($retval !== 0) { //error_log('Command \''.$command.'\' exited with code '.$retval); error_log('['.date("Y/m/d H:i:s e").'] Command \''.$command.'\' exited with code '.$retval.', response was:'."\n".implode("\n", $output)."\n\n", 3, '/var/log/gitflash'); } else if (strpos(implode($output), 'MY_SERVERS_MESSAGE') !== false) { error_log('['.date("Y/m/d H:i:s e").'] Command \''.$command.'\' exited with code '.$retval.', response was:'."\n".implode("\n", $output)."\n\n", 3, '/var/log/gitflash'); } } catch (Exception $e) { error_log('['.date("Y/m/d H:i:s e").'] Command \''.$command.'\' exited with code '.$retval.' with exception:'."\n".$e->getMessage()."\n\n", 3, '/var/log/gitflash'); } } // command // init (default) // activate // status // update // flush // reinit // deactivate if ($cli) { if ($argc > 1) $command = $argv[1]; if ($argc > 2) $commitmsg = $argv[2]; } else { $command = $_POST['command']; $commitmsg = $_POST['commitmsg']; } if (empty($command)) $command='init'; if (empty($commitmsg)) $commitmsg='Config change'; $loadingMessage = ''; switch ($command) { case 'activate': $loadingMessage = 'Activating'; break; case 'deactivate': $loadingMessage = 'Deactivating'; break; case 'update': $loadingMessage = 'Updating'; break; case 'reinit': $loadingMessage = 'Reinitializing'; break; case 'status': $loadingMessage = 'Loading'; break; } load_flash_backup_state(); // keyfile if (!file_exists('/var/local/emhttp/var.ini')) { response_complete(406, '{"error":"Machine still booting"}'); } $var = parse_ini_file("/var/local/emhttp/var.ini"); $keyfile = @file_get_contents($var['regFILE']); if ($keyfile === false) { response_complete(406, '{"error":"Registration key required"}'); } $keyfile = @base64_encode($keyfile); // check if activated if ($command != 'activate') { exec('git -C /boot config --get remote.origin.url 2>&1', $config_output, $return_var); if (($return_var != 0) || (strpos($config_output[0],'backup.unraid.net') === false)) { $arrState['activated'] = false; response_complete(406, '{"error":"Not activated"}'); } } // if flush command, invoke our background rc.flash_backup to flush if ($command == 'flush') { exec('/etc/rc.d/rc.flash_backup flush &>/dev/null'); response_complete(200, '{}'); } if (!empty($loadingMessage)) { save_flash_backup_state($loadingMessage); } // if deactivate command, just remove our origin if ($command == 'deactivate') { exec_log('git --git-dir /boot/.git remote remove origin'); exec('/etc/rc.d/rc.flash_backup stop &>/dev/null'); response_complete(200, '{}'); } // build a list of sha256 hashes of the bzfiles $bzfilehashes = []; $allbzfiles = ['bzimage','bzfirmware','bzmodules','bzroot','bzroot-gui']; foreach ($allbzfiles as $bzfile) { $sha256 = trim(@file_get_contents("/boot/$bzfile.sha256")); if (strlen($sha256) != 64) { response_complete(406, '{"error":"Invalid or missing '.$bzfile.'.sha256 file"}'); } $bzfilehashes[] = $sha256; } $ch = curl_init('https://keys.lime-technology.com/backup/flash/activate'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'keyfile' => $keyfile, 'version' => $var['version'], 'bzfiles' => implode(',', $bzfilehashes) ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($result === false) { response_complete(500, '{"error":"'.$error.'"}'); } $json = json_decode($result, true); if (empty($json) || empty($json['ssh_privkey'])) { response_complete(406, $result); } // save the public and private keys if (!file_exists('/root/.ssh')) { mkdir('/root/.ssh', 0700); } if ($json['ssh_privkey'] != file_get_contents('/root/.ssh/unraidbackup_id_ed25519')) { file_put_contents('/root/.ssh/unraidbackup_id_ed25519', $json['ssh_privkey']); chmod('/root/.ssh/unraidbackup_id_ed25519', 0600); file_put_contents('/root/.ssh/unraidbackup_id_ed25519.pub', $json['ssh_pubkey']); chmod('/root/.ssh/unraidbackup_id_ed25519.pub', 0644); } // add configuration to use our keys if (!file_exists('/root/.ssh/config') || strpos(file_get_contents('/root/.ssh/config'),'Host backup.unraid.net') === false) { file_put_contents('/root/.ssh/config', 'Host backup.unraid.net IdentityFile ~/.ssh/unraidbackup_id_ed25519 IdentitiesOnly yes ', FILE_APPEND); chmod('/root/.ssh/config', 0644); } // add our server as a known host if (!file_exists('/root/.ssh/known_hosts') || strpos(file_get_contents('/root/.ssh/known_hosts'),'backup.unraid.net,54.70.72.154') === false) { file_put_contents('/root/.ssh/known_hosts', 'backup.unraid.net,54.70.72.154 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKrKXKQwPZTY25MoveIw7fZ3IoZvvffnItrx6q7nkNriDMr2WAsoxu0DrU2QrSLH5zFF1ibv4tChS1hOpiYObiI='."\n", FILE_APPEND); chmod('/root/.ssh/known_hosts', 0644); } // blow away existing repo if reinit command if ($command == 'reinit' && file_exists('/boot/.git')) { exec_log('rm -rf /boot/.git'); } // ensure git repo is setup on the flash drive if (!file_exists('/boot/.git/info/exclude')) { exec_log('git init /boot'); } // setup a nice git description if (!file_exists('/boot/.git/description') || strpos(file_get_contents('/boot/.git/description'),$var['NAME']) === false) { file_put_contents('/boot/.git/description', 'Unraid flash drive for '.$var['NAME']."\n"); } // configure git to use the noprivatekeys filter exec_log('git -C /boot config filter.noprivatekeys.clean /usr/local/emhttp/plugins/dynamix.unraid.net/scripts/git-noprivatekeys-clean'); // configure git to apply the noprivatekeys filter to wireguard config files if (!file_exists('/boot/.gitattributes') || strpos(file_get_contents('/boot/.gitattributes'),'noprivatekeys') === false) { file_put_contents('/boot/.gitattributes', '# file managed by Unraid, do not modify config/wireguard/*.cfg filter=noprivatekeys config/wireguard/*.conf filter=noprivatekeys config/wireguard/peers/*.conf filter=noprivatekeys '); } // setup git ignore for files we dont need in the flash backup if (!file_exists('/boot/.git/info/exclude') || strpos(file_get_contents('/boot/.git/info/exclude'),'peers') === false) { file_put_contents('/boot/.git/info/exclude', '# file managed by Unraid, do not modify # Blacklist everything /* # Whitelist selected root files !*.sha256 !changes.txt !license.txt !startup.nsh !EFI*/ EFI*/boot/* !EFI*/boot/syslinux.cfg !syslinux/ syslinux/* !syslinux/syslinux.cfg !syslinux/syslinux.cfg- # Whitelist entire config directory !config/ # except for selected files config/drift config/forcesync config/plugins/unRAIDServer.plg config/random-seed config/shadow config/smbpasswd config/plugins/**/*.tgz config/plugins/**/*.txz config/plugins/**/*.tar.bz2 config/plugins-error config/plugins-old-versions config/plugins/dockerMan/images config/wireguard/peers/*.png '); } // ensure git user is configured exec_log('git --git-dir /boot/.git config user.email \'gitbot@unraid.net\''); exec_log('git --git-dir /boot/.git config user.name \'gitbot\''); // ensure upstream git server is configured and in-sync exec('git --git-dir /boot/.git remote add -f -t master -m master origin git@backup.unraid.net:~/flash.git &>/dev/null'); if ($command != 'reinit') { exec_log('git --git-dir /boot/.git reset origin/master'); exec_log('git --git-dir /boot/.git checkout -B master origin/master'); } // establish status exec('git -C /boot status --porcelain 2>&1', $status_output, $return_var); $arrState['activated'] = $return_var==0; if ($return_var != 0) { $arrState['loading'] = ''; response_complete(406, '{"error":"'.${status_output[0]}.'"}'); } $arrState['uptodate'] = empty($status_output); if ($command == 'status') { $data = implode("\n", $status_output); response_complete($httpcode, '{"data":"'.$data.'"}', $data); } if (($command == 'update') || ($command == 'reinit')) { // push changes upstream if (!empty($status_output)) { exec_log('git -C /boot add -A'); if ($command == 'reinit') { exec_log('git -C /boot commit -m \'Initial commit\''); exec_log('git -C /boot push --force --set-upstream origin master'); } else { exec_log('git -C /boot commit -m ' . escapeshellarg($commitmsg)); exec_log('git -C /boot push --set-upstream origin master', $status_output, $return_var); if ($return_var != 0) { exec_log('git -C /boot push --force --set-upstream origin master'); } } $arrState['uptodate'] = true; } } if ($command == 'activate') { exec('/etc/rc.d/rc.flash_backup start &>/dev/null'); } response_complete($httpcode, '{}'); ?> ]]> function handleMessage(e) { //if (e.origin != "http://child.com") { return; } if (e.data.length == 0) { return; } const SAFE_JSON_PARSE = (str) => { try { return [null, JSON.parse(str)]; } catch (err) { return [err]; } }; const [err, data] = SAFE_JSON_PARSE(e.data); if (err) return false; // swallow json parse error const HANDLE_LICENSES = (data, e) => { if (data.license) { $.get('/webGui/include/InstallKey.php', {url: data.license}, function() { console.log('New license key installed: ' + data.license); const payload = { event: 'LICENSE_PINGBACK', message: 'New license key installed', license: data.license, success: true, }; e.source.postMessage(JSON.stringify(payload), e.origin); }).fail(function() { console.error('Failed to license new key: ' + data.license); const payload = { event: 'LICENSE_PINGBACK', message: 'Failed to license new key', license: data.license, success: false, }; e.source.postMessage(JSON.stringify(payload), e.origin); }); } else { console.error('KEY_PURCHASE event fired! but missing license data:', data); } }; switch (data.event) { case "CLOSE_SHADOWBOX": hideRegWizard(); break; case "SHUTDOWN": window.location.href = '/webGui/include/Boot.php'; break; case "REG_WIZARD": if (data.apikey) { var postargs = { '#file': 'dynamix/dynamix.cfg', '#section': 'remote', apikey: data.apikey, regWizTime: `${Date.now()}_${data.guid}`, // set when signing in the first time and never unset for the sake of displaying Sign In/Up in the UPC without needing to validate guid every time }; if (data.email) { postargs['email'] = data.email; } if (data.username) { postargs['username'] = data.username; } if (data.avatar) { postargs['avatar'] = data.avatar; } $.post('/update.php', postargs, function() { console.log('dynamix/dynamix.cfg: Updated apikey under [remote] section'); // send a ping back to the regwiz const payload = { event: 'ACCOUNT_PINGBACK', success: true, type: 'signIn', webGuiPathname: window.location.pathname }; e.source.postMessage(JSON.stringify(payload), e.origin); }).fail(function() { console.error('Failed to update apikey under [remote] section'); // send a ping back to the regwiz const payload = { event: 'ACCOUNT_PINGBACK', success: false, type: 'signIn', webGuiPathname: window.location.pathname }; e.source.postMessage(JSON.stringify(payload), e.origin); }); } // duplicate conditional so we don't get the error from HANDLE_LICENSES() if (data.license) HANDLE_LICENSES(data, e); break; case "KEY_PURCHASE": HANDLE_LICENSES(data, e); break; case "GET_STATE": $.get('/plugins/dynamix.unraid.net/include/state.php', function(newstate) { e.source.postMessage(newstate, e.origin); }).fail(function(err) { console.error('Failed to get new state: ' + err); }); break; case "SUCCESS_NEW_STATE": $.get('/plugins/dynamix.unraid.net/include/state.php', function(newstate) { // parse response so we can update the event type string to send back const [stateError, stateData] = SAFE_JSON_PARSE(newstate); const newPayload = { ...stateData, event: 'DELIVER_NEW_STATE', success: true }; e.source.postMessage(JSON.stringify(newPayload), e.origin); }).fail(function(err) { e.source.postMessage(JSON.stringify({ event: 'DELIVER_NEW_STATE', success: false }), e.origin); }); break; case "MYSERVERS_UNREGISTER": $.post('/update.php', { '#file': 'dynamix/dynamix.cfg', '#section': 'remote', apikey: '', avatar: '', email: '', username: '', }, function() { console.log('dynamix/dynamix.cfg: Unregistered myservers, cleared apikey under [remote] section'); // send a ping back to the regwiz const payload = { event: 'ACCOUNT_PINGBACK', success: true, type: 'signOut', webGuiPathname: window.location.pathname }; e.source.postMessage(JSON.stringify(payload), e.origin); }).fail(function() { console.error('Failed to unregister'); // send a ping back to the regwiz const payload = { event: 'ACCOUNT_PINGBACK', success: false, type: 'signOut', webGuiPathname: window.location.pathname }; e.source.postMessage(JSON.stringify(payload), e.origin); }); break; case "RELOAD": return window.location.reload(); case "REDIRECT_MAIN": return window.location.href = '/Main'; case "STOP_SENDING_SESSION": break; case "PREFLIGHT_REQUEST": break; default: console.error('Unhandled event \'' + data.event + '\' fired. data:', data); break; } } window.addEventListener('message', handleMessage, false); ]]> https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js $MANIFEST_JSON echo "📋 Received manifest" # Take each key and add it to MANIFEST_TXT KEYS=($((<${MANIFEST_JSON} jq -r 'keys | @sh') | tr -d \'\")) for ix in ${!KEYS[*]} do echo "${BASE_URL}${KEYS[$ix]}" >> $MANIFEST_TXT echo "✨ ${BASE_URL}${KEYS[$ix]}" done # Download files from the manifest file echo "🚀 Parsed Manifest – starting downloads" xargs -n 1 curl --http2 -s -O < $MANIFEST_TXT # Done echo "✅ Registration Wizard files downloaded" if [ -f "$MANIFEST_TXT" ]; then echo "☄️🚮 Deleted temp files" rm $MANIFEST_TXT fi fi # Copy files to webGui's public dir mkdir -p /usr/local/emhttp/webGui/wc/ rsync -r --exclude="$MANIFEST_JSON" /boot/config/plugins/Unraid.net/wc/ /usr/local/emhttp/webGui/wc/ ]]> ]]> ]]> $var['deviceCount'], "email" => ($remote['email']) ? $remote['email'] : '', "flashproduct" => $var['flashProduct'], "flashvendor" => $var['flashVendor'], "guid" => $var['flashGUID'], "internalip" => $_SERVER['SERVER_ADDR'], "internalport" => $_SERVER['SERVER_PORT'], "keyfile" => str_replace(['+','/','='], ['-','_',''], trim(base64_encode(@file_get_contents($var['regFILE'])))), "protocol" => $_SERVER['REQUEST_SCHEME'], "reggen" => (int)$var['regGen'], "registered" => empty($remote['apikey']) || empty($var['regFILE']) ? 0 : 1, "servername" => $var['NAME'], "serverip" => $_SERVER['SERVER_ADDR'], "site" => $_SERVER['REQUEST_SCHEME']."://".$_SERVER['HTTP_HOST'], "state" => strtoupper(empty($var['regCheck']) ? $var['regTy'] : $var['regCheck']), ]; // upc translations $upc_translations = [ ($_SESSION['locale']) ? $_SESSION['locale'] : 'en_US' => [ 'getStarted' => _('Get Started'), 'signIn' => _('Sign In'), 'signUp' => _('Sign Up'), 'signOut' => _('Sign Out'), 'error' => _('Error'), 'fixError' => _('Fix Error'), 'closeLaunchpad' => _('Close Launchpad and continue to webGUI'), 'learnMore' => _('Learn more'), 'popUp' => _('Pop-up'), 'close' => _('Close'), 'backToPopUp' => sprintf(_('Back to %s'), _('Pop-up')), 'closePopUp' => sprintf(_('Close %s'), _('Pop-up')), 'contactSupport' => _('Contact Support'), 'lanIp' => sprintf(_('LAN IP %s'), '{0}'), 'continueToUnraid' => _('Continue to Unraid'), 'year' => _('year'), 'years' => _('years'), 'month' => _('month'), 'months' => _('months'), 'day' => _('day'), 'days' => _('days'), 'hour' => _('hour'), 'hours' => _('hours'), 'minute' => _('minute'), 'minutes' => _('minutes'), 'second' => _('second'), 'seconds' => _('seconds'), 'ago' => _('ago'), 'basicPlusPro' => [ 'heading' => _('Thank you for choosing Unraid OS!'), 'message' => [ 'registered' => _('Get started by signing in to Unraid.net'), 'upgradeEligible' => _('To support more storage devices as your server grows, click Upgrade Key') ] ], 'actions' => [ 'purchase' => _('Purchase Key'), 'upgrade' => _('Upgrade Key'), 'recover' => _('Recover Key'), 'replace' => _('Replace Key'), 'extend' => _('Extend Trial'), ], 'upc' => [ 'avatarAlt' => _("{0} Avatar"), 'confirmClosure' => _('Confirm closure then continue to webGUI'), 'closeDropdown' => _('Close dropdown'), 'openDropdown' => _('Open dropdown'), 'pleaseConfirmClosureYouHaveOpenPopUp' => _('Please confirm closure. You have an open pop-up.'), 'trialHasExpiredSeeOptions' => _('Trial has expired, see options below'), 'extraLinks' => [ 'newTab' => sprintf(_('Opens %s in new tab'), '{0}'), 'myServers' => _('My Servers Dashboard'), 'forums' => _('Unraid Forums'), 'settings' => [ 'text' => _('Settings'), 'title' => _('Settings > Management Access • Unraid.net'), ], ], 'meta' => [ 'trial' => [ 'active' => [ 'date' => sprintf(_('Trial key expires at %s'), '{date}'), 'timeDiff' => sprintf(_('Trial expires in %s'), '{timeDiff}'), ], 'expired' => [ 'date' => sprintf(_('Trial key expired at %s'), '{date}'), 'timeDiff' => sprintf(_('Trial expired %s'), '{timeDiff}'), ], ], 'uptime' => [ 'date' => sprintf(_('Server up since %s'), '{date}'), 'readable' => sprintf(_('Uptime %s'), '{timeDiff}'), ], ], 'myServers' => [ 'heading' => _('My Servers'), 'beta' => _('beta'), 'errors' => [ 'unraidApi' => [ 'heading' => _('Unraid API Error'), 'message' => _('Failed to connect to Unraid API'), ], 'myServers' => [ 'heading' => _('My Servers Error'), 'message' => _('Please wait a moment and reload the page'), ], ], 'closeDetails' => _('Close Details'), 'loading' => _('Loading My Servers data'), 'displayingLastKnown' => _('Displaying last known server data'), 'mothership' => [ 'connected' => _('Connected to Mothership'), 'notConnected' => _('Not Connected to Mothership'), ], 'accessLabels' => [ 'current' => _('Current server'), 'local' => _('Local access'), 'offline' => _('Server Offline'), 'remote' => _('Remote access'), 'unavailable' => _('Access unavailable'), ], ], 'opensNewHttpsWindow' => [ 'base' => sprintf(_('Opens new HTTPS window to %s'), '{0}'), 'signIn' => sprintf(_('Opens new HTTPS window to %s'), _('Sign In')), 'signIn' => sprintf(_('Opens new HTTPS window to %s'), _('Sign Out')), 'purchase' => sprintf(_('Opens new HTTPS window to %s'), _('Purchase Key')), 'upgrade' => sprintf(_('Opens new HTTPS window to %s'), _('Upgrade Key')), ], 'signInActions' => [ 'resolve' => _('Sign In to resolve'), 'purchaseKey' => _('Sign In to Purchase Key'), 'purchaseKeyOrExtendTrial' => _('@:upc.signInActions.purchaseKey or @:actions.extend'), ], ], 'stateData' => [ 'ENOKEYFILE' => [ 'humanReadable' => _('No Keyfile'), 'heading' => [ 'registered' => _('Thanks for supporting Unraid!'), 'notRegistered' => _("Let's unleash your hardware!"), ], 'message' => [ 'registered' => _('You are all set 👍'), 'notRegistered' => _('Sign in or sign up to get started'), ], ], 'TRIAL' => [ 'humanReadable' => _('Trial'), 'heading' => _('Thank you for choosing Unraid OS!'), 'message' => _('Your Trial key includes all the functionality and device support of a Pro key') . ' ' . _('After your Trial has reached expiration, your server still functions normally until the next time you Stop the array or reboot your server') . '' . _('At that point you may either purchase a license key or request a Trial extension'), '_extraMsg' => sprintf(_('You have %s remaining on your Trial key'), '{parsedExpireTime}'), ], 'EEXPIRED' => [ 'humanReadable' => _('Trial Expired'), 'heading' => _('Your Trial has expired'), 'message' => [ 'base' => _('To continue using Unraid OS you may purchase a license key'), 'extensionNotEligible' => _('You have used all your Trial extensions') .' @:stateData.EEXPIRED.message.base', 'extensionEligible' => '@:stateData.EEXPIRED.message.base ' . _('Alternately, you may request a Trial extension'), ], ], 'BASIC' => [ 'humanReadable' => _('Basic'), ], 'PLUS' => [ 'humanReadable' => _('Plus'), ], 'PRO' => [ 'humanReadable' => _('Pro'), ], 'EGUID' => [ 'humanReadable' => _('GUID Error'), 'error' => [ 'heading' => _('Registration key / GUID mismatch'), 'message' => [ 'default' => _('The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /boot/config directory on your USB Flash boot device.'), 'replacementEligible' => _('@:stateData.EGUID.error.message.default or click Replace Key to transfer your registration to this new flash device.'), ], ], ], 'ENOKEYFILE2' => [ 'humanReadable' => _('Missing key file'), 'error' => [ 'heading' => _('@:stateData.ENOKEYFILE2.humanReadable'), 'message' => _('It appears that your license key file is corrupted or missing. The key file should be located in the /boot/config directory on your USB Flash boot device. If you do not have a backup copy of your license key file you may attempt to recover your key. If this was a Trial installation, you may purchase a license key.'), ], ], 'ETRIAL' => [ 'humanReadable' => _('Invalid installation'), 'error' => [ 'heading' => _('@:stateData.ETRIAL.humanReadable'), 'message' => _('It is not possible to use a Trial key with an existing Unraid OS installation. You may purchase a license key corresponding to this USB Flash device to continue using this installation.'), ], ], 'ENOKEYFILE1' => [ 'humanReadable' => _('No Keyfile'), 'error' => [ 'heading' => _('No USB flash configuration data'), 'message' => _('There is a problem with your USB Flash device'), ], ], 'ENOFLASH' => [ 'humanReadable' => _('No Flash'), 'error' => [ 'heading' => _('Cannot access your USB Flash boot device'), 'message' => _('There is a physical problem accessing your USB Flash boot device'), ], ], 'EGUID1' => [ 'humanReadable' => _('Multiple License Keys Present'), 'error' => [ 'heading' => _('@:stateData.EGUID1.humanReadable'), 'message' => _('There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /boot/config directory on your USB Flash boot device. Alternately you may purchase a license key for this USB flash device. If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.'), ], ], 'EBLACKLISTED' => [ 'humanReadable' => _('BLACKLISTED'), 'error' => [ 'heading' => _('Blacklisted USB Flash GUID'), 'message' => _('This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device. A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers.'), ], ], 'EBLACKLISTED1' => [ 'humanReadable' => _('@:stateData.EBLACKLISTED.humanReadable'), 'error' => [ 'heading' => _('USB Flash device error'), 'message' => _('This USB Flash device has an invalid GUID. Please try a different USB Flash device.'), ], ], 'EBLACKLISTED2' => [ 'humanReadable' => _('@:stateData.EBLACKLISTED.humanReadable'), 'error' => [ 'heading' => _('USB Flash has no serial number'), 'message' => _('@:stateData.EBLACKLISTED.error.message'), ], ], 'ENOCONN' => [ 'humanReadable' => _('Trial Requires Internet Connection'), 'error' => [ 'heading' => _('Cannot validate Unraid Trial key'), 'message' => _('Your Trial key requires an internet connection. Please check Settings > Network.'), ], ], 'STALE' => [ 'humanReadable' => _('Stale'), 'error' => [ 'heading' => _('Stale Server'), 'message' => _('Please refresh the page to ensure you load your latest configuration'), ], ], ], 'regWizPopUp' => [ 'regWiz' => _('Registration Wizard'), 'toHome' => _('To Registration Wizard Home'), 'continueTrial' => _('Continue Trial'), 'serverInfoToggle' => _('Toggle server info visibility'), 'youCanSafelyCloseThisWindow' => _('You can safely close this window'), 'automaticallyClosingIn' => _('Automatically closing in'), 'byeBye' => _('bye, bye 👋'), 'browserWillSelfDestructIn' => _('Browser will self destruct in'), 'closingPopUpMayLeadToErrors' => _('Closing this pop-up window while actions are being preformed may lead to unintended errors'), 'goBack' => _('Go Back'), 'shutDown' => _('Shut Down'), 'haveAccountSignIn' => _('Already have an account? Sign In'), 'noAccountSignUp' => _("Don't have an account? Sign Up"), 'serverInfo' => [ 'flash' => _('Flash'), 'product' => _('Product'), 'GUID' => _('GUID'), 'name' => _('Name'), 'ip' => _('IP'), ], 'forms' => [ 'displayName' => _('Display Name'), 'emailAddress' => _('Email Address'), 'displayNameOrEmailAddress' => _('Display Name or Email Address'), 'displayNameRootMessage' => _('Use your Unraid.net credentials, not your local server credentials'), 'honeyPotCopy' => _('If you fill this field out then your email will not be sent'), 'fieldRequired' => _('This field is required'), 'submit' => _('Submit'), 'submitting' => _('Submitting'), 'notValid' => _('Form not valid'), 'cancel' => _('Cancel'), 'confirm' => _('Confirm'), 'createMyAccount' => _('Create My Account'), 'subject' => _('Subject'), 'password' => _('Password'), 'togglePasswordVisibility' => _('Toggle Password Visibility'), 'message' => _('Message'), 'confirmPassword' => _('Confirm Password'), 'passwordMinimum' => _('8 or more characters'), 'comments' => _('comments'), 'newsletterCopy' => _('Sign me up for the monthly Unraid newsletter: a digest of recent blog posts, community videos, popular forum threads, product announcements, and more'), 'terms' => [ 'iAgree' => _('I agree to the'), 'text' => _('Terms of Use'), ], ], 'routes' => [ 'extendTrial' => [ 'heading' => [ 'loading' => _('Extending Trial'), 'error' => _('Trial Extension Failed'), ], ], 'forgotPassword' => [ 'heading' => _('Forgot Password'), 'subheading' => _("After resetting your password come back to the Registration Wizard pop-up window to Sign In and complete your server's registration"), 'resetPasswordNow' => _('Reset Password Now'), 'backToSignIn' => _('Back to Sign In'), ], 'signIn' => [ 'heading' => [ 'signIn' => _('Unraid.net Sign In'), 'recover' => _('Unraid.net Sign In to Recover Key'), 'replace' => _('Unraid.net Sign In to Replace Key'), ], 'subheading' => _('Please sign in with same email with which you purchased your license'), 'form' => [ 'replacementConditions' => [ 'name' => _('Acknowledge Replacement Conditions'), 'label' => _('I acknowledge that replacing a license key results in permanently blacklisting the previous USB Flash GUID'), ], 'label' => [ 'password' => [ 'replace' => _('Unraid.net account password'), ], ], ], ], 'signUp' => [ 'heading' => _('Sign Up for Unraid.net'), 'subheading' => _('This setup will help you get your server up and running'), ], 'signOut' => [ 'heading' => _('Unraid.net Sign Out'), 'subheading' => _('This will remove your server from displaying in My Servers'), ], 'success' => [ 'heading' => [ 'username' => sprintf(_('Hi %s'), '{0}'), 'default' => _('Success!'), ], 'subheading' => [ 'extention' => _('Your trial will expire in 15 days'), 'newTrial' => _('Your trial will expire in 30 days'), ], 'signIn' => [ 'tileTitle' => [ 'actionFail' => sprintf(_('%s was not signed in to your Unraid.net account'), '{0}'), 'actionSuccess' => sprintf(_('%s is signed in to your Unraid.net account'), '{0}'), 'loading' => sprintf(_('Signing in %s to Unraid.net account'), '{0}'), ], ], 'signOut' => [ 'tileTitle' => [ 'actionFail' => sprintf(_('%s was not signed out of your Unraid.net account'), '{0}'), 'actionSuccess' => sprintf(_('%s was signed out of your Unraid.net account'), '{0}'), 'loading' => sprintf(_('Signing out %s from Unraid.net account'), '{0}'), ], ], 'keys' => [ 'trial' => _('Trial'), 'basic' => _('Basic'), 'plus' => _('Plus'), 'pro' => _('Pro'), ], 'extended' => sprintf(_('%s Key Extended'), '{0}'), 'recovered' => sprintf(_('%s Key Recovered'), '{0}'), 'replaced' => sprintf(_('%s Key Replaced'), '{0}'), 'created' => sprintf(_('%s Key Created'), '{0}'), 'install' => [ 'loading' => sprintf(_('Installing %s Key'), '{0}'), 'error' => sprintf(_('%s Key Install Error'), '{0}'), 'success' => sprintf(_('Installed %s Key'), '{0}'), ], 'timeout' => sprintf(_('Communication with %s has timed out'), '{0}'), 'loading1' => _('Please keep this window open'), 'loading2' => _('Were working our magic'), 'countdown' => [ 'success' => [ 'prefix' => _('Auto closing in'), 'text' => _('You can safely close this window'), ], 'error' => [ 'prefix' => _('Auto redirecting in'), 'text' => _('Back to Registration Home'), 'complete' => _('Back in a flash ⚡️'), ], ], ], 'troubleshoot' => [ 'heading' => [ 'default' => _('Troubleshoot'), 'success' => _('Thank you for contacting Unraid'), ], 'subheading' => [ 'default' => _("Forgot what Unraid.net account you used? Have a USB flash device that already has an account associated with it? Just give us the details about what happened and we'll do our best to get you up and running again."), 'success' => _('We have received your e-mail and will respond in the order it was received. While we strive to respond to all requests as quickly as possible, please allow for up to 3 business days for a response.'), ], 'relevantServerData' => _('Your USB Flash GUID and other relevant server data will also be sent'), ], 'verifyEmail' => [ 'heading' => _('Verify Email'), 'form' => [ 'verificationCode' => _('verification code'), 'verifyCode' => _('Paste or Enter code'), ], 'noCode' => _("Didn't get code?"), ], 'whatIsUnraidNet' => [ 'heading' => _('What is Unraid.net?'), 'subheading' => _('Expand your servers capabilities'), 'copy' => _('With an Unraid.net account you can start using My Servers (beta) which gives you access to the following features:'), 'features' => [ 'secureRemoteAccess' => [ 'heading' => _('Secure remote access'), 'copy' => _("Whether you need to add a share, container, or virtual machine, do it all from the webGui from anytime and anywhere using HTTPS. Best of all, all SSL certificates are verified by Let's Encrypt, so no browser security warnings."), ], 'realTimeMonitoring' => [ 'heading' => _('Real-time Monitoring'), 'copy' => _('Get quick real-time info on the status of your servers such as storage, container, and VM usage. And not just for one server, but all the servers in your Unraid fleet!'), ], 'usbFlashBackup' => [ 'heading' => _('USB Flash Backup'), 'copy' => _('Click a button and your flash is automatically backed up to Unraid.net, enabling easy recovery in the event of a device failure. Never self-manage/host your flash backups again!'), ], 'regKeyManagement' => [ 'heading' => _('Registration key management'), 'copy' => _('Download any registration key linked to your account. Upgrade keys to higher editions.'), ], ], ], 'notFound' => [ 'subheading' => _('Page Not Found'), ], 'notAllowed' => [ 'subheading' => _('Page Not Allowed'), ], ], ], ], ]; ?> " > ]]> ]]> /\n<\/head>/g' > /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php sed -i $'/