]> ##&name; ###&version; - initial release if [ -e /etc/rc.d/rc.unraid-api ]; then /etc/rc.d/rc.flash_backup stop /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-node-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 fi "", "username" => "", "avatar" => "", "wanaccess" => "no", "wanport" => "443" ]; } if (empty($remote['username'])) { $remote['username'] = ''; } if (empty($remote['avatar'])) { $remote['avatar'] = ''; } $arr = []; $arr['event'] = 'STATE'; $arr['ts'] = time(); $arr['deviceCount'] = $var['deviceCount']; $arr['guid'] = $var['flashGUID']; $arr['state'] = $license_state; $arr['keyfile'] = $key_contents; $arr['reggen'] = $var['regGen']; $arr['flashproduct'] = $var['flashProduct']; $arr['flashvendor'] = $var['flashVendor']; $arr['servername'] = $var['NAME']; $arr['serverip'] = $_SERVER['SERVER_ADDR']; $arr['internalip'] = $_SERVER['SERVER_ADDR']; $arr['internalport'] = $_SERVER['SERVER_PORT']; $arr['protocol'] = $_SERVER['REQUEST_SCHEME']; $arr['registered'] = empty($remote['apikey']) || empty($var['regFILE']) ? 0 : 1; $arr['username'] = $remote['username']; $arr['avatar'] = $remote['avatar']; $arr['locale'] = $_SESSION['locale'] ? $_SESSION['locale'] : 'en_US'; echo json_encode($arr); ?> ]]> &node-api; env="&env;" 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/node-api/.env.staging $node_base_directory/node-api/.env elif [[ $currentEnv = "staging" ]]; then echo "Switching from staging to production" echo 'env="production"' > $envFile cp $node_base_directory/node-api/.env.production $node_base_directory/node-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 .env ]; then # Load Environment Variables export $(egrep -v '^#' $node_base_directory/node-api/.env | xargs) fi # Start node-api ENVIRONMENT=$(echo $env) node $node_base_directory/node-api/index.js &> /dev/null & cd $old_working_directory # wait until node_api_pid exists for i in {1..10}; do local node_api_pid=$(pidof node-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/node-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/node-api/.env ]; then # Load Environment Variables export $(egrep -v '^#' $node_base_directory/node-api/.env | xargs) fi # Start node-api ENVIRONMENT=$(echo $env) DEBUG=true node $node_base_directory/node-api/index.js } _stop() { local node_api_pid=$(pidof node-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 node-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 _start status exit 0 } _install() { # Install node-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/unraid-${download}.tgz --strip 1 done # 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 ;; 'stop') stop ;; 'reload') reload ;; 'install') install ;; '_install') _install ;; 'uninstall') uninstall ;; *) echo "usage $0 status|start|report|switch-env|start-debug|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! "", "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: : > 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.
  :
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} | 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} | 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) 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']); } // 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', $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('git --git-dir /boot/.git remote remove origin &>/dev/null'); 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') { exec('rm -rf /boot/.git'); } // ensure git repo is setup on the flash drive if (!file_exists('/boot/.git/info/exclude')) { exec('git init /boot &>/dev/null'); } // 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"); } // setup git ignore for files we dont need in the flash backup if (strpos(file_get_contents('/boot/.git/info/exclude'),'forcesync') === false) { file_put_contents('/boot/.git/info/exclude', '# Unraid OS Flash Backup # 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 '); } // ensure git user is configured exec('git --git-dir /boot/.git config user.email \'gitbot@unraid.net\' &>/dev/null'); exec('git --git-dir /boot/.git config user.name \'gitbot\' &>/dev/null'); // 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('git --git-dir /boot/.git reset origin/master &>/dev/null'); exec('git --git-dir /boot/.git checkout -B master origin/master &>/dev/null'); } // establish status exec('git -C /boot status --porcelain', $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('git -C /boot add -A &>/dev/null'); if ($command == 'reinit') { exec('git -C /boot commit -m \'Initial commit\' &>/dev/null'); exec('git -C /boot push --force --set-upstream origin master &>/dev/null'); } else { exec('git -C /boot commit -m ' . escapeshellarg($commitmsg) . ' &>/dev/null'); exec('git -C /boot push --set-upstream origin master &>/dev/null'); } $arrState['uptodate'] = true; } } if ($command == 'activate') { exec('/etc/rc.d/rc.flash_backup start &>/dev/null'); } response_complete($httpcode, '{}'); ?> ]]> #sb-title {display:none} #sb-info {display:none} ]]> 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']), ]; ?> " > ]]> ]]> /\n<\/head>/g' > /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php sed -i $'/